#production ready authentication

1 messages · Page 1 of 1 (latest)

last hare
#

Hi,

I was using aws Cognito authentication with my Nextjs App. Currently seems not possible to use it with remix. Which method/stack or service do you recommend?

Cognito is nice because made simple 2FA, and verification links very easy to manage, you do not have to manage a database for authentication and it is free for the first 50K users.

Have you got any suggestion?

latent steeple
#

I'd like to know more about this as well. We are creating a multi-tenant app and very seriously considering Remix but are considering Cognito for auth as well.

last hare
#

step 4

#

but the next part of the code inside cognitoUser.authenticatedUser() seems to not work:

//POTENTIAL: Region needs to be set if not already set previously elsewhere.
        AWS.config.region = '<region>';

        AWS.config.credentials = new AWS.CognitoIdentityCredentials({
            IdentityPoolId: '...', // your identity pool id here
            Logins: {
                // Change the key below according to the specific region your user pool is in.
                'cognito-idp.<region>.amazonaws.com/<YOUR_USER_POOL_ID>': result
                    .getIdToken()
                    .getJwtToken(),
            },
        });

        //refreshes credentials using AWS.CognitoIdentity.getCredentialsForIdentity()
        AWS.config.credentials.refresh(error => {
            if (error) {
                console.error(error);
            } else {
                // Instantiate aws sdk service objects now that the credentials have been updated.
                // example: var s3 = new AWS.S3();
                console.log('Successfully logged!');
            }
        });
#

this is using import * as AWS from 'aws-sdk/global';

#

but I do not know exactly why you need this part, I am still investigating

#

Working with Remix: AWS Amplify Authentication Using Authenticator UI and AppSync Integration
#remix #aws #authentication

Walkthrough a code sample of integrating AWS Amplify with a Remix Application. We show how to implement complete authentication flows to your application with minimal boilerplate. We then make a database query using the AWS...

▶ Play video
finite arch
#

I'm using amazon-cognito-identity-js and I'm successfully getting tokens from cognito
I recall having an issue with authenticateUser as well but it's working for me now

#
const cognitoDomain = process.env.COGNITO_DOMAIN;
const clientId = process.env.CLIENT_ID;

const cookieSettings: CookieOptions = {
    maxAge: 60 * 60 * 30,
    secure: process.env.NODE_ENV === "production",
    secrets: [process.env.SESSION_SECRET as string],
    httpOnly: true,
};

var poolData: ICognitoUserPoolData = {
    UserPoolId: process.env.REACT_APP_COGNITO_USER_POOL_ID as string, // Your user pool id here
    ClientId: process.env.CLIENT_ID as string, // Your client id here
};
var userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);

export async function cognitoLogin(username: string, password: string) {
    var userData = {
        Username: username,
        Pool: userPool,
    };
    var cognitoUser = new AmazonCognitoIdentity.CognitoUser(userData);

    var authenticationData = {
        Username: username,
        Password: password,
    };
    var authenticationDetails = new AmazonCognitoIdentity.AuthenticationDetails(authenticationData);

    return new Promise((resolve, reject) => {
        console.debug("Logging in");
        cognitoUser.authenticateUser(authenticationDetails, {
            onSuccess: function (result) {
                const tokens = {
                    accessToken: result.getAccessToken().getJwtToken(),
                    idToken: result.getIdToken().getJwtToken(),
                    refreshToken: result.getRefreshToken().getToken(),
                };

                AWS.config.region = process.env.REACT_APP_COGNITO_REGION as string;

                resolve({
                    username: username,
                    tokens,
                });
            },

            onFailure: function (err) {
                reject(err);
                console.error("error", err);
            },
        });
    });
}
#

imports (discord made me split the message. Sorry for doing it backwards lol)

import { createCookie, redirect } from "@remix-run/node";
import { CookieOptions } from "@remix-run/server-runtime";
import * as AmazonCognitoIdentity from "amazon-cognito-identity-js";
import { ICognitoUserPoolData } from "amazon-cognito-identity-js";
import * as AWS from "aws-sdk/global";
import jwt_decode from "jwt-decode";
unborn trench
# last hare Hi, I was using aws Cognito authentication with my Nextjs App. Currently seems ...

you can use Remix Auth (https://github.com/sergiodxa/remix-auth) and then setup it to use Cognito, I know a few people in the community created their own Cognito strategies for Remix Auth but I think they're not published, however it should be a matter of extending the OAuth2Strategy (https://github.com/sergiodxa/remix-auth-oauth2) and customize it for Cognito (set the URLs and how to fetch the user profile)

jovial maple
last hare
jovial maple
last hare
# jovial maple I'm wondering to understand more about this topic using Cognito. I found this ar...

Interesting article, I am checking the docs. The best thing I see in using the sdk is that you get familiar with it and can be use in the same way for all the services. The drawback is that it is more complex to setup credentials to work with it ... and it harder to get familiar with it. For example to signIn an user the method is initiateAuth , I spend a while looking for SignIn in the docs... 😄

last hare
jovial maple
# last hare Interesting article, I am checking the docs. The best thing I see in using the s...

Yes. I think SDK allows you to do all things on server-side, - e.g. client will not call Cognito directly.

An idea for discussion, I'd use CDK to prepare all resources like Cognito, the authflow is ADMIN_USER_PASSWORD_AUTH . Then in auth.server.ts, we could use AdminInitiateAuthCommand instead of initiateAuth to sign in users to handle SignIn. With that can be called from action in the SignIn page. Then, with the response data from Cognito, we use something like Remix Stack do, createUserSession . Here I'm not sure what to use for USER_SESSION_KEY - for now maybe just userId . Still thinking how to save the accessToken and other tokens in the session for access other recourses if any?

#
import type {
  AdminInitiateAuthCommandInput,
  AdminInitiateAuthCommandOutput
} from "@aws-sdk/client-cognito-identity-provider";
import {
  AdminInitiateAuthCommand, CognitoIdentityProviderClient, NotAuthorizedException
} from "@aws-sdk/client-cognito-identity-provider";

import crypto from "crypto";
import invariant from "tiny-invariant";

const clientId = process.env.USER_POOL_CLIENT_ID || "";
const userPoolId = process.env.USER_POOL_ID || "";
const clientSecret = process.env.USER_POOL_CLIENT_SECRET || "";

invariant(process.env.REGION, "REGION must be set");
invariant(process.env.USER_POOL_ID, "USER_POOL_ID must be set");
invariant(clientId, "USER_POOL_CLIENT_ID must be set");
invariant(clientSecret, "USER_POOL_CLIENT_SECRET must be set");

// create a client can be shared by different commands.
const client = new CognitoIdentityProviderClient({
  region: process.env.REGION,
});

// sign in
export async function signIn({
  email,
  password,
}: {
  email: string;
  password: string;
}): Promise<AdminInitiateAuthCommandOutput | Error | undefined> {
  const input: AdminInitiateAuthCommandInput = {
    ClientId: clientId,
    AuthFlow: "ADMIN_USER_PASSWORD_AUTH",
    AuthParameters: {
      USERNAME: email,
      PASSWORD: password,
      SECRET_HASH: hashSecret({
        clientSecret,
        email,
        clientId,
      }),
    },
    UserPoolId: userPoolId,
  };
  const command = new AdminInitiateAuthCommand(input);

  try {
    const data = await client.send(command);
    console.log("Sign In successfully");
    return data;
  } catch (error) {
    console.log("Sign In failed");
    if (error instanceof NotAuthorizedException) {
      return error;
    }
    return error as Error;
  }
}

export function hashSecret({
  clientSecret,
  email,
  clientId,
}: {
  clientSecret: string;
  email: string;
  clientId: string;
}) {
  return crypto
    .createHmac("SHA256", clientSecret)
    .update(email + clientId)
    .digest("base64");
}

#

A sample auth.server.ts 👆

finite arch
#

So I switched to amplify library (which still uses cognito), MUCH easier btw

finite arch
#

this is my login code now

import { Auth } from "aws-amplify";

export const awsConfig = {
    aws_cognito_region: process.env.REACT_APP_COGNITO_REGION,
    aws_user_pools_id: process.env.REACT_APP_COGNITO_USER_POOL_ID as string,
    aws_user_pools_web_client_id: process.env.CLIENT_ID,
    oauth: {},
};

Auth.configure(awsConfig);


export async function cognitoLogin(username: string, password: string) {

    const user = await Auth.signIn(username, password);
    
    return user;
}


#

I have full access to the users cognito information including the authenticationId for the identity system. So this seems to be the best way to go IMO.

The only thing I haven't been able to figure out is how to share that Auth class with the browser code so I can subscribe to appsync graphql subscriptions or how to allow the stupid access token/idtoken to init a session with the amplify library. They want you to login again...

spare fern
last hare
#

Hi @spare fern I am finally using the library https://www.npmjs.com/package/amazon-cognito-identity-js , it is very easy to work with and you do not need to have an account or role configured in aws to connect directly with the sdk (I understood that you need that when I checked it). You have use cases in the docs. I had problems because I was trying to get the response of a method without make a Promise but when you understand it , it is so easy.

#

I did not have time to check other things, but I will check it in the future.