AWS Cognito External User Pool Identity Provider(OIDC) - amazon-cognito

I'm using Cognito App Client integration with external provider(Twitch)
User authentication works fine, but as code from auth server is consumed by Cognito, I'm not sure how should I send Twitch requests with token, which I'd normally get from twitch I Cognito wouldn't consume this code. I only have Cognito code, which I can use in https://{my-domain}/oauth2/token requests in exchange for Cognito tokens. request returns id_token, access_token and refresh_token, which decoded look like
id token
{
"at_hash": "yTNkeTAqzqcXCYi3yLL2Pw",
"sub": "3cfba641-4058-475f-9818-17291175fd31",
"cognito:groups": [
"us-east-1_xxxxxxxxxxxx"
],
"iss": "https://cognito-idp.us-east-1.amazonaws.com/us-east-1_xxxxxxxxxxxx",
"cognito:username": "xxxxxxxxxxxx",
"preferred_username": "xxxxxxxxxxxx",
"nonce": "SxxlipCDVbXbcXa1H7Uf9_nM0uOurAAObUVCyreBDDux99QoAngUoiGdE0me-0Zon6fEVLLTSqD4EN1Y6_lFm48MaoBaxyywZCQKOT70gfQEfkuhlsjImJd1ko3qH3QKdlmvWSPCUZoACPYNSgR364VPELyQTVMkRTCt9eYROag",
"aud": "35l1cn53cnj9sv1ndu8u01amk0",
"identities": [
{
"userId": "xxxxxxxxxxxx",
"providerName": "xxxxxxxxxxxx",
"providerType": "OIDC",
"issuer": null,
"primary": "true",
"dateCreated": "1588191000072"
}
],
"token_use": "id",
"auth_time": 1588191003,
"exp": 1588194603,
"iat": 1588191003
}
access token
{
"sub": "3cfba641-4058-475f-9818-17291175fd31",
"cognito:groups": [
"us-east-1_xxxxxxxxxxxx"
],
"token_use": "access",
"scope": "aws.cognito.signin.user.admin phone openid profile email",
"auth_time": 1588191003,
"iss": "https://cognito-idp.us-east-1.amazonaws.com/us-east-1_xxxxxxxxxxxx",
"exp": 1588194603,
"iat": 1588191003,
"version": 2,
"jti": "55863213-c764-4b07-a386-a9c93d14e4b2",
"client_id": "xxxxxxxxxxxx",
"username": "xxxxxxxxxxxx"
}
How can I get user token to call Twitch API (for example GET https://api.twitch.tv/helix/users endpoint with authorized user's token)

CAUTION - Doing it incorrectly, you expose sensitive attributes to client.
You need to create 2 versions of attributes - custom and dev:custom, map oidc provider attributes to custom ones (looks like dev:custom aren't mappable), then in TokenGeneration_HostedAuth trigger you need to get these custom attributes, set dev:custom ones, then delete customs.
Seems like a tweak, but I don't see another way of doing it and keeping tokens safe.
Solution for that is to create custom attributes in your user pool, then map these attributes for identity provider. Looks something like:
'custom:refresh_token': refresh_token
'custom:id_token': id_token
'custom:access_token': access_token
Cloudformation template for that:
user pool
....
Schema: [
{
AttributeDataType: 'String',
DeveloperOnlyAttribute: true,
Mutable: true,
Name: 'refresh_token',
Required: false,
},
{
AttributeDataType: 'String',
DeveloperOnlyAttribute: true,
Mutable: true,
Name: 'access_token',
Required: false,
},
{
AttributeDataType: 'String',
DeveloperOnlyAttribute: true,
Mutable: true,
Name: 'id_token',
Required: false,
},
{
AttributeDataType: 'String',
Mutable: true,
Name: 'refresh_token',
Required: false,
},
{
AttributeDataType: 'String',
Mutable: true,
Name: 'access_token',
Required: false,
},
{
AttributeDataType: 'String',
Mutable: true,
Name: 'id_token',
Required: false,
},
],
....
user pool identity provider
....
AttributeMapping: {
'custom:refresh_token': 'refresh_token',
'custom:access_token': 'access_token',
'custom:id_token': 'id_token',
},
....

Related

Amplify Cognito Google invalid email address format

I would like to identify users by email in a project using AWS Amplify + Google federation.
I was getting good results with usernames until I tried to switch to emails. The error shows up as error_description in the callback URL as follows
https://domain.tld/?error_description=Invalid+email+address+format.+&state=some-state&error=invalid_request
What should go in cli-inputs.json / Cognito configuration to support email based identification?
I don't know about the Google federation, but I think you must be using 'email' from the start (amplify add auth). You can't switch to email after the fact, you'd need to make a new pool and migrate users into it.
I use 'email' for the username. Here's my cli-inputs.json, hope it helps.
{
"version": "1",
"cognitoConfig": {
"identityPoolName": "xxxxxa96d_identitypool_0821234d",
"allowUnauthenticatedIdentities": false,
"resourceNameTruncated": "xxxxx0821234",
"userPoolName": "xxxxxa96d_userpool_081234d",
"autoVerifiedAttributes": [
"email"
],
"mfaConfiguration": "OFF",
"mfaTypes": [
"SMS Text Message"
],
"smsAuthenticationMessage": "Your authentication code is {####}",
"smsVerificationMessage": "Your verification code is {####}",
"emailVerificationSubject": "Your verification code",
"emailVerificationMessage": "Your verification code is {####}",
"defaultPasswordPolicy": false,
"passwordPolicyMinLength": 8,
"passwordPolicyCharacters": [],
"requiredAttributes": [
"email"
],
"aliasAttributes": [],
"userpoolClientGenerateSecret": false,
"userpoolClientRefreshTokenValidity": 30,
"userpoolClientWriteAttributes": [
"email"
],
"userpoolClientReadAttributes": [
"email"
],
"userpoolClientLambdaRole": "xxxx_userpoolclient_lambda_role",
"userpoolClientSetAttributes": false,
"sharedId": "081234d",
"resourceName": "xxxx0821234d",
"authSelections": "identityPoolAndUserPool",
"useDefault": "manual",
"usernameAttributes": [
"email"
],
"userPoolGroupList": [],
"serviceName": "Cognito",
"usernameCaseSensitive": false,
"useEnabledMfas": true,
"authRoleArn": {
"Fn::GetAtt": [
"AuthRole",
"Arn"
]
},
"unauthRoleArn": {
"Fn::GetAtt": [
"UnauthRole",
"Arn"
]
},
"breakCircularDependency": true,
"dependsOn": [],
"thirdPartyAuth": false,
"userPoolGroups": false,
"adminQueries": false,
"triggers": {},
"hostedUI": false,
"authProviders": [],
"parentStack": {
"Ref": "AWS::StackId"
},
"permissions": []
}
}

IdentityServer4 {"error":"invalid_client"}

I am using IdentityServer4 (version 3.0.2.0) and facing no client id issue. The exact error is
ERROR| No client with id 'myclientId' found. aborting
Startup.cs of IdentityServer4 project
services.AddIdentityServer()
.AddDeveloperSigningCredential()
// .AddInMemoryCaching()
.AddInMemoryApiResources(Configuration.GetSection("IdentityServer:ApiResources"))
.AddInMemoryClients(Configuration.GetSection("IdentityServer:Clients"))
.AddOperationalStore(options =>
{
options.ConfigureDbContext = builder =>
builder.UseSqlServer(connectionString,
sql => sql.MigrationsAssembly(migrationsAssembly));
// this enables automatic token cleanup. this is optional.
options.EnableTokenCleanup = Convert.ToBoolean(Configuration["CleanUp:IsEnabled"]);
options.TokenCleanupInterval = Convert.ToInt32(Configuration["CleanUp:Interval"]); // interval in seconds
});
Also, I have sha256 converted client_secret in appsettings.json file, sample appsettings.json
"IdentityServer": {
"ApiResources": [
{
"Name": "myapi",
"DisplayName": "my api",
"Scopes": [
{
"Name": "mycustomscope"
},
{
"Name": "openid"
}
],
"ApiSecrets": [
{
"Value": "my sha256 converted secret string",
"Description": "my api"
}
]
}
],
"Clients": [
{
"Enabled": true,
"ClientId": "myclientId",
"AccessTokenLifetime": 100000000,
"ProtocolType": "oidc",
"RequireClientSecret": true,
"IdentityTokenLifetime": 300,
"AuthorizationCodeLifetime": 300,
"ConsentLifetime": 300,
"AbsoluteRefreshTokenLifetime": 2592000,
"SlidingRefreshTokenLifetime": 1296000,
"RefreshTokenExpiration": true,
"AlwaysSendClientClaims": false,
"ClientName": "myclientId",
"ClientSecrets": [
{
"Value": "my sha256 converted secret string",
"Type": "SharedSecret"
}
],
"AllowedGrantTypes": [ "client_credentials", "password" ],
"AllowedScopes": [ "mycustomscope", "openid" ],
"RequireConsent": true
}
]
}
Sample token request from postman/JMeter
url: https://myip:port/myappPool/connect/token
method type: POST
Parameters are:
{
"client_id":"myclientId",
"client_secret": "plaintext secret",
"username":"abcdefghijkl",
"scope":"mycustomscope",
"device_id":"custom property",
"password": "mypassword",
"grant_type":"password",
"app_version":"custom property",
"hashed_value":"custom property"
}
I am posting answer to my own question because I have solved the issue. For me below given field was making an issue. After removing this field, the code ran just fine.
"RefreshTokenExpiration": true
Turns out, IdentityServer4.Models.Client does not have any boolean field named RefreshTokenExpiration but class object.

AddInMemoryClients results in Unknown client or not enabled

I'm trying to get Identity server 4 to work in an ASP Net Core 3 application with an Angular 8 SPA using "oidc-client": "1.10.1".
If I add the following to my appsettings.json
"IdentityServer": {
"Key": {
"Type": "File",
"FilePath": "acertificate.pfx",
"Password": "notmyrealpassword..orisit?"
},
"Clients": {
"dev-client": {
"Profile": "IdentityServerSPA",
}
}
}
Using this client:
{
authority: 'https://localhost:5001/',
client_id: 'dev-client',
redirect_uri: 'http://localhost:4200/auth-callback',
post_logout_redirect_uri: 'http://localhost:4200/',
response_type: 'id_token token',
scope: 'openid profile API',
filterProtocolClaims: true,
loadUserInfo: true
}
I get: Invalid redirect_uri: http://localhost:4200/auth-callback
adding.
"dev-client": {
"Profile": "IdentityServerSPA",
"RedirectUris": [ "http://localhost:4200/auth-callback" ]
}
does nothing. If I add the Client config copied (almost) from the documentation
"Clients": [
{
"Enabled": true,
"ClientId": "dev-client",
"ClientName": "Local Development",
"AllowedGrantTypes": [ "implicit" ],
"AllowedScopes": [ "openid", "profile", "API" ],
"RedirectUris": [ "http://localhost:4200/auth-callback" ],
"RequireConsent": false,
"RequireClientSecret": false
}
]
I get: System.InvalidOperationException: 'Type '' is not supported.' at startup
If I try to configure the client in code, and only keep the "Key" section in appsettings
services
.AddIdentityServer(options =>
{
options.Cors.CorsPolicyName = _CorsPolicyName;
})
.AddInMemoryClients(new IdentityServer4.Models.Client[] {
new IdentityServer4.Models.Client
{
ClientId = "dev-client",
ClientName = "JavaScript Client",
ClientUri = "http://localhost:4200",
AllowedGrantTypes = { IdentityModel.OidcConstants.GrantTypes.Implicit },
AllowAccessTokensViaBrowser = true,
RedirectUris = { "http://localhost:4200/auth-callback" },
PostLogoutRedirectUris = { "http://localhost:4200" },
AllowedCorsOrigins = { "http://localhost:4200" },
AllowedScopes =
{
IdentityServer4.IdentityServerConstants.StandardScopes.OpenId,
IdentityServer4.IdentityServerConstants.StandardScopes.Profile,
IdentityServer4.IdentityServerConstants.StandardScopes.Email,
"API"
}
}
})
I get: Unknown client or not enabled: dev-client.
Someone help me keep my sanity and point out my, most likely obvious, error.
ASP.NET Identity overrides the documented method for IdentityServer Clients configuration, expecting a dictionary of well-known values. You can bypass this by creating a section that is not named Clients and reading from that section explicitly. Additionally, AddApiAuthorization exposes the Clients collection on the ApiAuthorizationOptions, which can be used to add other clients:
.AddApiAuthorization<...>(options =>
{
options.Clients.AddRange(Configuration.GetSection("IdentityServer:OtherClients").Get<Client[]>());
});

Auth.currentAuthenticatedUser not loading name and family name attributes (and others) from Cognito

I'm using the Auth.currentAuthenticatedUser method to retrieve the attributes recorded for the logged user from AWS Cognito - but only basic atributes are showing. The ones I want are "name" and "family name", but they don't seem to be loaded in the Promise.
This is only the beggining, but I'm concerned as I will want to retrieve other attributes which are not showing up, like user picture, for instance.
Tried to use currentAuthenticatedUser and currentUserInfo with the same results.
async componentDidMount() {
await Auth.currentAuthenticatedUser({bypassCache: true})
.then ( user => this.setUserInfo( user ) )
.catch( err => console.log(err))
}
CognitoUser {
"Session": null,
"attributes": Object {
"email": "r...#gmail.com",
"email_verified": true,
"phone_number": "+5...",
"phone_number_verified": false,
"sub": "246e9...",
},
"authenticationFlowType": "USER_SRP_AUTH",
"client": Client {
"endpoint": "https://cognito-idp.us-east-2.amazonaws.com/",
"userAgent": "aws-amplify/0.1.x react-native",
},
"deviceKey": undefined,
"keyPrefix": "CognitoIdentityServiceProvider.12ddetjn0c0jo0npi6lrec63a7",
"pool": CognitoUserPool {
"advancedSecurityDataCollectionFlag": true,
"client": Client {
"endpoint": "https://cognito-idp.us-east-2.amazonaws.com/",
"userAgent": "aws-amplify/0.1.x react-native",
},
"clientId": "12ddetjn0c0jo0npi6lrec63a7",
"storage": [Function MemoryStorage],
"userPoolId": "us-east...",
},
"preferredMFA": "NOMFA",
"signInUserSession": CognitoUserSession {
"accessToken": CognitoAccessToken {
"jwtToken": "e...oJPg",
"payload": Object {
"auth_time": 1565137817,
"client_id": "1...6lrec63a7",
"event_id": "c3...-4bd9-ad42-200f95f9921c",
"exp": 15...2,
"iat": 156...5872,
"iss": "https://cognito-idp.us-east-2.amazonaws.com/us-east-...",
"jti": "5483e...544149c42e58",
"scope": "aws.cognito.signin.user.admin",
"sub": "246e93...f4d8e6f4725b",
"token_use": "access",
"username": "r...f",
},
},
"clockDrift": -2,
"idToken": CognitoIdToken {
"jwtToken": "eyJraWQiOiJk...",
"payload": Object {
"aud": "12ddetjn0c0j..rec63a7",
"auth_time": 1565137817,
"cognito:username": "r..",
"email": "r..#gmail.com",
"email_verified": true,
"event_id": "c3ae..200f95f9921c",
"exp": ..2,
"iat": ..2,
"iss": "https://cognito-idp.us-east-2.amazonaws.com/us-east-..",
"phone_number": "+5...3",
"phone_number_verified": false,
"sub": "246e937..f4d8e6f4725b",
"token_use": "id",
},
},
"refreshToken": CognitoRefreshToken {
"token": "eyJjd...",
},
},
"storage": [Function MemoryStorage],
"userDataKey": "CognitoIdentityServiceProvider.12ddetjn0....userData",
"username": "r...ff",
}
To get all user attributes, you may be looking for the Auth.userAttributes() function. To use this you want something like this code:
const authUser = await Auth.currentAuthenticatedUser();
const attributes = await Auth.userAttributes(authUser);
// the next line is a convenience that moves the attributes into
// the authUser object
attributes.forEach((attr) => {
authUser.attributes[attr.Name] = attr.Value;
});
If you're still not getting the attributes you need, take a look here, and you can see that you can enable the reading of other attributes from the Amplify command line.
So, in the root of your project:
Type "amplify update auth" at the console.
Select "Walkthrough the auth configurations"
Step through making all the same selections as you've done before.
When it asks, "Do you want to specify the user attributes this app can read and write?" it's "Y", and then you select the attributes you want to be able to read.
When you finish the wizard, use "amplify push auth"
When that's completed, try re-running.
As an alternative to steps 1-4 above, you can also edit cli-inputs.json in the amplify\backend\auth<your auth config name> directory. It's in "userpoolClientReadAttributes". Simply add the attributes you would like to this array (e.g. "name").
This answer was verified with amplify CLI version 8.1.0.

Loopback unauthorized request when trying to login

I have created a custom user model that inherits form the built-in User model. Then I added a boot script that creates a default user.
User.create([{
username: 'admin',
email: 'email#email.com',
password: 'admin'
}], on_usersCreated);
This creates a document in the monogdb database,. However when I try to login, it send a 401 unauthorized request.
I tried to find the root of the problem, and it seems that loopback cannot fins such user in the database. More specifically, in node_modules/loopback/common/models/user there is this:
self.findOne({where: query}, function(err, user) {
var defaultError = new Error('login failed');
defaultError.statusCode = 401;
....
where
query = { email: "email#email.com" }
But the problem is that it cannot find a user with that email in the database although it does exist.
This is the user.json file
{
"name": "user",
"base": "User",
"idInjection": true,
"properties": {},
"validations": [],
"relations": {},
"acls": [
{
"accessType": "*",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "DENY"
},
{
"accessType": "*",
"principalType": "ROLE",
"principalId": "admin",
"permission": "ALLOW"
}
],
"methods": []
}
I managed to solve this issue. As #superkhau stressed out, you should use the custom User model to create the default users.
Additionally, I had to expose the user model API and hide the build-in User API; So in model-config.js
...
"User": {
"dataSource": "db",
"public": false
},
"user": {
"dataSource": "db",
"public": true
},
...
You might have two collections: User and user. In your models, user extends from User. Which model did you use the create the user record?