Customizing Cognito Hosted UI - amazon-cognito

We are using Cognito Hosted UI with Local Account login and few SAML providers. There is an option to customize some styling but I am looking for couple of additional things.
Adding a custom text and link to an external site - like terms and conditions
SAML provider name do not take a space. I cannot show something like "Login with X".
Is there a way to customize the Hosted UI to do these things? Thanks.

You would be better off not using Cognito provided UI, and only use Cognito for authentication and authorization.
My react web application interact with Cognito, without using any of its provided UI components. Only through this way, you are able to have absolute control of your frontend.
You can refer below sample code, and use it for your web application.
Sample code
The sample code shows you how to signin by calling Cognito with using amazon-cognito-identity-js. Or some other reference: Implementing AWS-Cognito in Angular 2 and Using AWS Cognito in a Lambda function with npm
import {
CognitoUserPool,
CognitoUser,
CognitoUserSession,
CognitoUserAttribute,
AuthenticationDetails,
} from "amazon-cognito-identity-js";
export function signIn(email: string, password: string) {
var authenticationData = {
Username: email,
Password: password,
};
const cognitoUser = new CognitoUser({
Username: email,
Pool: getPool(
process.env.REACT_APP_BUSINESS_ACCOUNT_USER_POOL_ID!,
process.env.REACT_APP_BUSINESS_ACCOUNT_USER_POOL_CLIENT_ID!
),
});
var authenticationDetails = new AuthenticationDetails(authenticationData);
return new Promise((resolve, reject) => {
cognitoUser.authenticateUser(authenticationDetails, {
onSuccess: (result) => resolve(result),
onFailure: (error) => reject(error),
newPasswordRequired: (userAttributes, requiredAttributes) => {
resolve({ needResetPassword: true, cognitoUser, userAttributes });
},
});
});
}

Related

How to integrate Hasura with Firebase Authentication

Does anyone here have experienced Hasura graphql ?
I started exploring #postgrest to fulfill my serverless needs, but I end up dumping it since its hard to make its authentication. Just last two day I learned that I can do phone number authentication with Firebase Authentication with my reactjs App.
Then I look back at my database solution, I learned that Apollo graphql and also Hasura may solve my database needs. I have no Idea whether I can integrate my phone auth so that It produce jwt and can be used in Hasura / Apollo maybe?
Cut story short, is Hasura famous among us here ? What do you guys use for Graphql authentication ? Can it integrate with Firebase authentication ?
any suggestions much appreciated!
I've integrated GraphQL + Firebase Auth with both frontend and backend system before. I didn't use Hasura, just a normal Apollo GraphQL, but the concept should be the same. Hope it can help
Frontend phone authentication
In this example the frontend was developed using Flutter.
Get firebaseToken from FirebaseAuth, passing to login Mutation with input firebaseToken. This login Mutation must return JWT token.
Store this JWT token securely in your app
await FirebaseAuth.instance.verifyPhoneNumber(
phoneNumber: phoneNumber,
verificationCompleted:
(PhoneAuthCredential credential) async {
final UserCredential cr =
await FirebaseAuth.instance
.signInWithCredential(credential);
final String firebaseToken =
await cr.user!.getIdToken();
final QueryResult qe = await runMutation(
{"firebaseToken": firebaseToken})
.networkResult!;
final String jwt =
Login$Mutation.fromJson(qe.data!)
.login
.jwtToken;
...
},
...
);
GraphQL
mutation Login($firebaseToken: String!) {
login(input: { firebaseToken: $firebaseToken }) {
jwtToken
}
}
Schema GQL
type Mutation {
login(input: LoginInput!): Login! }
input LoginInput {
firebaseToken: String!
}
type Login {
jwtToken: String!
}
Backend Auth Resolver
In this example, backend was developed using NestJS (node.js). First create Auth resolver with login Mutation that accept firebaseToken. This login Mutation need to verify Id Token (Using Firebase Admin SDK). After verify you can get decodedToken, either phone number or email depending on your Firebase project authentication settings. Use this info to create new user if not exist. Return JWT token using payload e:g {id: user.id}
#Mutation(() => LoginDTO)
async login(
#Args('input', { type: () => LoginInput }) input: LoginInput
): Promise<LoginDTO> {
const decodedToken = await this.firebaseAuth.app
.auth()
.verifyIdToken(input.firebaseToken);
const number = (
decodedToken.firebase.identities.phone[0] as string
).substring(1);
const user = await this.passengerService.findOrCreateUserWithMobileNumber(
number
);
const payload = { id: user.id };
return {
jwtToken: this.jwtService.sign(payload),
};
}

How to use Auth0's CredentialsManager for Local Biometric Auth with React Native

The auth0 documentation on their credentials manager states
The credentials manager is an easy to use source of Keychain-based
authentication for iOS and Android, and should be usable with
auth.credentialsManager
When trying to use this suggested method
const isLoggedIn = await auth0.credentialsManager.hasValidCredentials();
This error is being thrown
undefined is not an object (evaluating '_$$_REQUIRE(_dependencyMap[10],
"../context/actions/authActions").auth0.credentialsManager.getCredentials')
Here's an overview of our auth0 configuration, and how it works currently
in AuthActions.js
export const auth0 = new Auth0({
domain: Config.AUTH0_DOMAIN,
clientId: Config.AUTH0_CLIENT_ID,
});
export const actionLogin = async (callback) => {
try {
const authState = await auth0.webAuth.authorize({
scope: 'openid profile email offline_access',
audience: Config.AUTH0_AUDIENCE,
prompt: 'login',
});
let response = await getState(authState, callback);
return response
} catch (e) {
console.log('Error Authenticating: ', e)
}
The hasValidCredentials() method mentioned above is called after a user has successfully authenticated with the webAuth, and it should be returning something along the lines of an access token, refresh token, id, and email per the docs
Note that we are trying to use this so that we can stop using the react-native-keychain package and use auth0's implementation of the native keystores by
await auth0.credentialsManager.requireLocalAuthentication();

Google OAuth2 with Passport and Express

I am struggling with getting Google OAuth to work with my Express/React application whilst using Passport.js. I am using JWTs, not sessions.
In my React webapp client, I have a "login with Google" button that calls my backend API /auth/google/ with the following route setup in Express:
router.get('auth/google', passport.authenticate('google', {session: false, scope: ['email','profile']}) );
My Passport.js google strategy is:
const googleStrategy = new GoogleStrategy(
{
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: "http://localhost:3000/api/v1/auth/google/callback",
passReqToCallback : true
},
async (request, accessToken, refreshToken, profile, done) => {
try {
console.log('profile', profile);// ** CORRECT USER PRINTED **
let existingUser = await User.findOne({ 'google.id': profile.id });
// if user exists return the user
if (existingUser) {
console.log('Found existing user...');
return done(null, existingUser);
}
// if user does not exist create a new user
const newUser = new User({
method: 'google',
googleId: profile.id,
profileImage: profile.photos[0].value,
firstName: profile.name.givenName,
lastName: profile.name.familyName,
shortName: profile.displayName,
});
await newUser.save();
return done(null, newUser);
} catch (error) {
return done(error, false)
}
}
);
My Google developer dashboard is setup to call the following URL in my Express API backend upon successful authentication: /auth/google/callback
My Express route for this is defined as: router.get('auth/google/callback', passport.authenticate('google', {session: false}), authController.googleAuthCallback);
My Express googleAuthCallback function is defined as:
exports.googleAuthCallback = async (req, res) => {
console.log(req.user) // ** WRONG USER PRINTED HERE ** different from above user printed in google strategy
}
The strange this is when I console.log the profile variable in my googleStrategy, I get the right user profile information for the account from Google. This means the authentication vis a vis Google is fine. However, this same account is NOT being provided to my /auth/google/callback endpoint in the req.user object at that location. It is an entirely different account (it is the first value from my database of Users, which is authenticated using local authentication).
How do I get the user object back to my Express callback endpoint that I supplied to Google in the developer console as the authorized redirect URI?
As a general question, what happens after the strategy calls return done(null, existingUser);? I have no callback in the /auth/google route after the passport.authenticate() middleware is called so what happens next?
I am using "passport-google-oauth20": "^2.0.0"
My let existingUser = await User.findOne({ 'google.id': profile.id });
line was incorrect and was essentially returning no user. Mongoose does not complain and hence the strategy was just returning the first user from my database rather than the authenticated google user.

auth0 checkSession({}) returns login_required when logged in through social provider, but not when logging in via username/password

I have an Angular app that uses Auth0 for authentication, and I'm trying to use checkSession({}, …) to persist a user's session if the token hasn't expired yet.
When I log in with my username/pw that I set up for the site, this works fine when I reload the browser/navigate directly to a resource. However, when I log in using a social provider (such as Google), the checkSession({}, …) call on a page reload returns an error and forces the user to log in again.
Some of the relevant code (mostly from the auth0 tutorial(s)):
export class AuthService {
// Create Auth0 web auth instance
private _auth0 = new auth0.WebAuth({
clientID: AUTH_CONFIG.CLIENT_ID,
domain: AUTH_CONFIG.CLIENT_DOMAIN,
responseType: 'token',
redirectUri: AUTH_CONFIG.REDIRECT,
audience: AUTH_CONFIG.AUDIENCE,
scope: AUTH_CONFIG.SCOPE
});
accessToken: string;
userProfile: any;
expiresAt: number;
// Create a stream of logged in status to communicate throughout app
loggedIn: boolean;
loggedIn$ = new BehaviorSubject<boolean>(this.loggedIn);
loggingIn: boolean;
isAdmin: boolean;
// Subscribe to token expiration stream
refreshSub: Subscription;
constructor(private router: Router) {
// If app auth token is not expired, request new token
if (JSON.parse(localStorage.getItem('expires_at')) > Date.now()) {
this.renewToken();
}
}
...
handleAuth() {
// When Auth0 hash parsed, get profile
this._auth0.parseHash((err, authResult) => {
if (authResult && authResult.accessToken) {
window.location.hash = '';
this._getProfile(authResult);
} else if (err) {
this._clearRedirect();
this.router.navigate(['/']);
console.error(`Error authenticating: ${err.error}`);
}
this.router.navigate(['/']);
});
}
private _getProfile(authResult) {
this.loggingIn = true;
// Use access token to retrieve user's profile and set session
this._auth0.client.userInfo(authResult.accessToken, (err, profile) => {
if (profile) {
this._setSession(authResult, profile);
this._redirect();
} else if (err) {
console.warn(`Error retrieving profile: ${err.error}`);
}
});
}
private _setSession(authResult, profile?) {
this.expiresAt = (authResult.expiresIn * 1000) + Date.now();
// Store expiration in local storage to access in constructor
localStorage.setItem('expires_at', JSON.stringify(this.expiresAt));
this.accessToken = authResult.accessToken;
this.userProfile = profile;
if (profile) {
this.isAdmin = this._checkAdmin(profile);
}
...
}
...
get tokenValid(): boolean {
// Check if current time is past access token's expiration
return Date.now() < JSON.parse(localStorage.getItem('expires_at'));
}
renewToken() {
// Check for valid Auth0 session
this._auth0.checkSession({}, (err, authResult) => {
if (authResult && authResult.accessToken) {
this._getProfile(authResult);
} else {
this._clearExpiration();
}
});
}
}
(This is from a service that is called in many places within the app, including some route guards and within some components that rely on profile information. If more of the app code would be useful, I can provide it.)
Also note: AUTH_CONFIG.SCOPE = 'openid profile email'
So, the issue appears to not have been related to my app at all. When using Social Providers, Auth0 has an explicit note in one of their tutorials that really helped me out:
The issue with social providers is that they were incorrectly configured in my Auth0 dashboard, and needed to use provider-specific app keys.
Important Note: If you are using Auth0 social connections in your app,
please make sure that you have set the connections up to use your own
client app keys. If you're using Auth0 dev keys, token renewal will
always return login_required. Each social connection's details has a
link with explicit instructions on how to acquire your own key for the
particular IdP.
Comment was found on this page: https://auth0.com/blog/real-world-angular-series-part-7/

How to pass an Amplify Cognito session from react native to webview?

I have a react native app that renders a WebView of a Web app
The react native app uses Cognito and Amplify for authentication.
The web app also uses the same Cognito and Amplify for authentication.
I have a login flow built with in the react native that has email/password login and social media federated Oauth logins. Both these login flows successfully work in the react native space and return a
CognitoUserSession {
idToken: CognitoIdToken,
refreshToken: CognitoRefreshToken,
accessToken: CognitoAccessToken,
clockDrift: 0
}
When the react native app renders the WebView the web app is unauthenticated. I am able to pass the CognitoUserSession data into the WebView successfully. Unfortunately, I don't see a way to have Amplify re-authenticate with this session.
this is the mobileLogin function I wrote that works
import Amplify, { Auth } from 'aws-amplify';
import {
CognitoUser,
CognitoUserSession,
CognitoIdToken,
CognitoRefreshToken,
CognitoAccessToken,
} from 'amazon-cognito-identity-js';
window.mobileLogin = async function(mobileSession) {
amplify = Amplify.configure({
...config().amplify,
userPoolWebClientId: '', //switch to mobile client
});
const localSession = new CognitoUserSession({
IdToken: new CognitoIdToken({ IdToken: mobileSession.idToken.jwtToken }),
RefreshToken: new CognitoRefreshToken({ RefreshToken: mobileSession.refreshToken }),
AccessToken: new CognitoAccessToken({ AccessToken: mobileSession.accessToken.jwtToken }),
});
const localUser = new CognitoUser({
Username: mobileSession.accessToken.payload.username,
Pool: Auth.userPool,
Storage: Auth.userPool.storage,
});
localUser.setSignInUserSession(localSession);
// this seems like a hack
Auth.currentCredentials = async () => localSession;
try {
await Auth.currentSession();
console.warn(`mobile login current session!!`);
store.dispatch(silentReloginAction())
} catch (ex) {
console.warn(`mobile login ${ex}`);
}
};
}
For someone who still need this.
First, you need add oauth setting to your Web application's AwsExports.json.
const AwsExports = {
Auth: {
...
oauth: {
domain: 'xxx.auth.us-east-1.amazoncognito.com',
scope:['openid'],
redirectSignIn: 'https://example.com',
redirectSignOut: 'https://example.com',
responseType: 'token'
}
},
};
then you can pass token with uri.
const session = await Auth.currentSession(),
id_token = session.getIdToken().getJwtToken(),
access_token = session.getAccessToken().getJwtToken(),
uri = `https://example.com##id_token=${id_token}&access_token=${access_token}`;
You should actually setup oauth things.
Because webview is opened as part of Oauth flow, oauth loggin out flow can be executed.
so without proper setting of oauth, error comes up