Add role based authentication using next-auth in NextJS - amazon-web-services

I was implementing a role based authentication using next-auth v4 using CognitoProvider which I modified to add a role but the role attribute is not passed in the final session json
import NextAuth from "next-auth/next";
function CognitoProvider(options) {
return {
id: "cognito",
name: "Cognito",
type: "oauth",
wellKnown: `${options.issuer}/.well-known/openid-configuration`,
idToken: true,
profile(profile) {
console.log(profile);
return {
id: profile.sub,
name: profile.name,
email: profile.email,
image: profile.picture,
role: profile["cognito:groups"],
};
},
options,
};
}
export default NextAuth({
providers: [
CognitoProvider({
clientId: process.env.COGNITO_CLIENT_ID,
clientSecret: process.env.COGNITO_CLIENT_SECRET,
issuer: process.env.COGNITO_DOAMIN,
}),
],
callbacks: {
session: (props) => {
console.log(props);
return props.session;
},
},
});
Below is the console log of profile object
role: profile["cognito:groups"]
Actual Object
I have added a user to admin group and wanted him to access a specific route in my NextJS app.
Any help would be appreciated.

You need to configure the jwt and session callbacks to include more data in the session.
From Next-Auth docs:
If you want to make something available you added to the token [...] via the jwt() callback, you have to explicitly forward it here [the session() callback] to make it available to the client.
To add the user's role:
export default NextAuth({
// ...
callbacks: {
jwt({ token, account, profile }) {
if (account) {
// modify token
token.role = profile.role;
}
return token;
},
session({ session, token }) {
// Send properties to the client
if (session.user) {
// modify session
session.user.roles = token.role;
}
return session;
},
},
});
Then in your route, you would get the user's role from the session session.user.role

Related

AWS Cognito Pre authentication get user data

I can't extract the username/password pair from the request.
2022-11-30T03:11:13.958Z xxxxxxxxxxxxxxxxxxxxx INFO{
version: '1',
region: 'xxxxxxxxx',
userPoolId: 'xxxxxxxxxxxxx',
userName: 'test',
callerContext: {
awsSdkVersion: 'aws-sdk-unknown-unknown',
clientId: 'xxxxxxxxxxxxxxxxxxxxxxxxxxx'
},
triggerSource: 'PreAuthentication_Authentication',
request: { userAttributes: {}, validationData: null, userNotFound: true },
response: {}
}
Node script:
export const handler = async(event, context, callback) => {
console.log(event);
console.log(event.request);
console.log(context);
console.log(context.body);
const response = {
statusCode: 200,
body: JSON.stringify('Hello from Lambda!'),
};
return response;
};
Add all rights to Identity and Access Management (IAM), but unfortunately I can't see the password either, it's possible that it can't be extracted.
You (the AWS account user / role) cannot see, view, or extract the Cognito users' passwords ever, this is the purpose of Cognito as a service.
If you really want to do this you could do the signup outside of Cognito with your own system, then set the password manually for the user in Cognito with adminSetPassword.

AWS CDK - How to setup Cognito User Pool with "Authorization Code" flow

I'm currently trying to create an Amazon Cognito User Pool with OAuth flow "Authorization Code" via the AWS CDK as described in the documentation aws-cognito module.
Here is the typescript code of my stack:
import * as cdk from '#aws-cdk/core';
import { UserPool, VerificationEmailStyle, OAuthScope } from '#aws-cdk/aws-cognito';
import { Duration } from '#aws-cdk/core';
export class UserPoolStack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const userPool = new UserPool(this, 'stackoverflow-userpool', {
userPoolName: 'stackoverflow-userpool',
selfSignUpEnabled: true,
signInCaseSensitive: false,
userVerification: {
emailSubject: 'Verify your email!',
emailBody: 'Hello, Thanks for signing up! {##Verify Email##}',
emailStyle: VerificationEmailStyle.LINK
},
signInAliases: {
username: true,
email: true
},
requiredAttributes: {
email: true
},
passwordPolicy: {
minLength: 12,
requireLowercase: true,
requireUppercase: true,
requireDigits: true,
requireSymbols: true,
tempPasswordValidity: Duration.days(7)
}
});
const client = userPool.addClient('stackoverflow-userpool-localhost-client', {
userPoolClientName: 'stackoverflow-localhost-client',
oAuth: {
flows: { authorizationCodeGrant: true },
scopes: [OAuthScope.OPENID],
callbackUrls: ['http://localhost:4200/callback']
}
});
userPool.addDomain('stackoverflow-userpool-domain-prefix', {
cognitoDomain: {
domainPrefix: 'stackoverflow'
}
});
}
}
For whatever reason my "Cognito User Pool" is not enabled as an "Identity Provider" in my "App client". (see screenshot)
Is my configuration wrong? I can't find any property that would enable this.
Is it a bug or because the module is still experimental? What confuses me is why would they describe how to set up the "Authorization Code" flow in the documentation if it doesn't work...
at the present date, looks good and worked for me. Maybe an old version of aws-cdk? In any case, you can explicitly enable Cognito User Pool on the client
const client = userPool.addClient('stackoverflow-userpool-localhost-client', {
userPoolClientName: 'stackoverflow-localhost-client',
oAuth: {
flows: { authorizationCodeGrant: true },
scopes: [OAuthScope.OPENID],
callbackUrls: ['http://localhost:4200/callback']
},
supportedIdentityProviders: [
UserPoolClientIdentityProvider.AMAZON,
UserPoolClientIdentityProvider.COGNITO,
]
});

Access UserPoolId from aws-cdk Stack outside of aws-cdk

I deployed an AWS Cognito UserPool via aws-cdk as CognitoStack.
Now, I want to automate testing of a GraphQL API that uses said AWS Cognito UserPool for authentication.
How can I programmatically get the UserPoolId required for authentication from CogntioStack?
My CognitoStack is:
export class CognitoStack extends Stack {
public readonly userPool: UserPool;
constructor(scope: App, id: string, props?: StackProps) {
super(scope, id, props);
this.userPool = new UserPool(this, 'UserPool', {
signInAliases: {
email: true,
phone: false,
username: false,
preferredUsername: false,
},
autoVerify: {
phone: false,
email: false,
},
selfSignUpEnabled: true,
});
this.userPool.addClient('web', {
authFlows: {
refreshToken: true,
userSrp: true,
},
});
new CfnOutput(this, 'UserPoolId', {
value: this.userPool.userPoolId,
})
}
}
When I do cdk deploy CognitoStack I get:
Outputs:
CognitoStack.UserPoolId = eu-central-1_xafasds
CognitoStack.ExportsOutputRefUserPool6BA7E5F296FD7236 = eu-central-1_xasdfdd
However, when I inspect cdk.out/CognitoStack.template.json (which I easily could require in my test), there is no eu-central-1_xasdfdd appearing.
Best way I've found so far is use the --outputs-file flag when running the deploy command, and then I have a json file with all fields I need.
cdk deploy CognitoStack --outputs-file outputs.json

AWS Appsync 401 and 403 errors in React Native

I've been pulling out my hair trying to set Appsync and Cognito in my React Native app.
I've tried the two following ways:
Amplify.configure(config);
OR
Amplify.configure({
Auth: {
region: config.aws_cognito_region, // REQUIRED - Amazon Cognito Region
userPoolId: config.aws_user_pools_id, // OPTIONAL - Amazon Cognito User Pool ID
userPoolWebClientId: config.aws_user_pools_web_client_id, // User Pool App Client ID
},
});
AND
const client = new AWSAppSyncClient({
url: appSyncConfig.graphqlEndpoint,
region: appSyncConfig.region,
auth: {
type: appSyncConfig.authType,
jwtToken: async () => (await Auth.currentSession()).getIdToken().getJwtToken(),
},
});
OR
const client = new AWSAppSyncClient({
url: appSyncConfig.graphqlEndpoint,
region: appSyncConfig.region,
auth: {
type: appSyncConfig.authType,
apiKey: appSyncConfig.apiKey,
},
});
I've also followed these two tutorials Tackling user auth, Building a notes app.
In both cases, I get the following error in GraphQL with no description:
Error: Network error: Response not successful: Received status code
This is while in Authorization Type is Amazon Cognito User Pool. I've also tried AWS Identity and Access Management (IAM), but that gives me a 403 error. Can someone point me in a direction where I can debug this further?
It might be caused a typo in the docs / article you've read. Trying replacing :
auth: {
type: appSyncConfig.authType,
apiKey: appSyncConfig.apiKey
}
with :
auth: {
type: appSyncConfig.authenticationType,
apiKey: appSyncConfig.apiKey
}
I have the following code and its working for me:
import Amplify, { Auth } from 'aws-amplify';
import API, { graphqlOperation } from '#aws-amplify/api'
window.LOG_LEVEL = 'DEBUG';
Amplify.configure({
Auth: {
"identityPoolId":'ap-southeast-1:xxxxxx',
"mandatorySignIn": false,
"region": "ap-southeast-1",
"userPoolId": "ap-southeast-1_xxxx",
"userPoolWebClientId": "xxxxxxx"
},
API:{
"aws_appsync_graphqlEndpoint": 'https://xxxx.ap-southeast-1.amazonaws.com/graphql',
"aws_appsync_region": 'ap-southwest-1',
"aws_appsync_authenticationType": 'AMAZON_COGNITO_USER_POOLS',
"aws_appsync_apiKey": 'null',
}
});

Firebase as Identity Provider with Cognito / AWS

I am having a hard time using Firebase as an Open ID Connect provider.
Can you please further describe the steps you have been through before and after to make this work?
For information, here is what I have done so far:
In AWS Console:
1 - Create an IAM Identity Provider ( OpenID Connect ) and used securetoken.google.com/<FIREBASE_PROJECT_ID> as an URL, <FIREBASE_PROJECT_ID>for Audience
2 - Checked the Thumbprint manually (it matches the one generated by AWS)
3 - Created a role with the permissions to access the desired services
4 - Created an Identity Pool in Cognito and selected my newly created role in the 'Authenticated role' Dropdown
5 - Selected my Identity Provider under the Authentication Providers > OpenID category (format is therefore): securetoken.google.com/<FIREBASE_PROJECT_ID>
In my code (I am using Vue.js) here are the logical steps I went through:
Import / setup AWS SDK
Invoke Firebase Auth service
Create a new CognitoIdentity
Use the getOpenIdTokenForDeveloperIdentity and push the tokenID received from Firebase
The issue is that I keep getting "Missing credentials in config" errors.
The code:
import axios from 'axios';
const AWS = require('aws-sdk');
AWS.config.region = 'eu-west-1';
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: 'MY_COGNITO_POOL_ID',
});
export default {
name: 'My Vue.js component name',
data() {
return {
email: '',
password: '',
msg: '',
};
},
methods: {
submit() {
axios
.post(
'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPassword?key=MY_KEY',
{
email: this.email,
password: password,
returnSecureToken: true,
},
)
.then((res) => {
// stores tokens locally
localStorage.setItem('jwt', JSON.stringify(res.data));
const cognitoidentity = new AWS.CognitoIdentity();
const params = {
IdentityPoolId: 'MY_COGNITO_POOL_ID',
Logins: {
'securetoken.google.com/<PROJECT_ID>': res.data.idToken,
},
IdentityId: null,
TokenDuration: 3600,
};
cognitoidentity.getOpenIdTokenForDeveloperIdentity(params, (err, data) => {
if (err) console.log(err, err.stack); // an error occurred
else console.log(data); // successful response
});
});
},
},
};
Here are the resources I have used so far while attempting to make this work:
http://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_create_oidc_verify-thumbprint.html
Using Firebase OpenID Connect provider as AWS IAM Identity Provider
https://github.com/aws/amazon-cognito-identity-js/blob/master/examples/babel-webpack/src/main.jsx
http://docs.aws.amazon.com/cognitoidentity/latest/APIReference/API_GetCredentialsForIdentity.html
https://aws.amazon.com/blogs/mobile/understanding-amazon-cognito-authentication/
https://aws.amazon.com/blogs/mobile/understanding-amazon-cognito-authentication-part-2-developer-authenticated-identities/
https://aws.amazon.com/blogs/mobile/understanding-amazon-cognito-authentication-part-3-roles-and-policies/
https://aws.amazon.com/blogs/mobile/understanding-amazon-cognito-authentication-part-4-enhanced-flow/
The final code if that can be any help for anyone:
import axios from 'axios';
const AWS = require('aws-sdk');
const aws4 = require('aws4');
export default {
name: 'VUE_CPNT_NAME',
data() {
return {
email: '',
password: '',
msg: '',
idToken: '',
};
},
methods: {
submit() {
// Firebase SignIn API
// Doc: https://firebase.google.com/docs/reference/rest/auth/
axios
.post(
'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPassword?key=[MY_KEY]',
{
email: this.email,
password: this.password,
returnSecureToken: true,
},
)
.then((res) => {
this.idToken = res.data.idToken;
localStorage.setItem('jwt', JSON.stringify(res.data));
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: 'IDENTITY_POOL_ID',
Logins: {
'securetoken.google.com/<FIREBASE_PROJECT_ID>': res.data.idToken,
},
}, {
region: 'eu-west-1',
});
// AWS.config.crendentials.get() methods works as well
// or a call to cognitoidentity.getId() followed by a call to getCredentialsForIdentity()
// will achieve the same thing. Cool. But why!?
AWS.config.getCredentials((err) => {
if (err) {
console.log(err);
}
const request = {
host: 'API_GATEWAY_ENDPOINT.eu-west-1.amazonaws.com',
method: 'GET',
url: 'https://API_GATEWAY_ENDPOINT.eu-west-1.amazonaws.com/PATH',
path: '/API_ENDPOINT_PATH',
};
// Signing the requests to API Gateway when the Authorization is set AWS_IAM.
// Not required when Cognito User Pools are used
const signedRequest = aws4.sign(request,
{
secretAccessKey: AWS.config.credentials.secretAccessKey,
accessKeyId: AWS.config.credentials.accessKeyId,
sessionToken: AWS.config.credentials.sessionToken,
});
// removing the Host header to avoid errors in Chrome
delete signedRequest.headers.Host;
axios(signedRequest);
});
});
},
},
};
Try setting the login map i.e the firebase token in the CognitoIdentityCredentials object. See this doc.
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: 'MY_COGNITO_POOL_ID',
Logins: {
'securetoken.google.com/':
}
});
Try calling get method on the credentials object before initializing the Cognito client. You can also use getCredentials instead.
If the above steps do not work & they should, pass the credentials as an option while initializing the Cognito client. See this doc for options available while using the CognitoIdentity constructor.
const cognitoidentity = new AWS.CognitoIdentity({credentials: AWS.config.credentials});
If you still receive the error, try logging credentials object in the console after calling the get() method. Ideally, it should have temporary credentials (accessKey, secretKey & sessionToken)