I use Amazon Cognito Identity SDK for JavaScript to log in my users at www.mydomain.com.
I was wondering if it was possible to have my users automatically logged in on store.mydomain.com
For what i know Cognito stores the logintoken and other things in localstorage and that can't be reached from subdomains?
AWS Cognito JS SDK uses LocalStorage to store the authentication tokens by default. You can change this behavior and use cookies to store the tokens. Then you can use your parent domain name while setting the cookies and all your sub-domains can access this cookie.
To achieve this, you can use the CookieStorage class from the JS SDK while creating the CognitoUserPool objects.
TypeScript Code snippets below
Full implementation can be found here. A running application can be viewed here.
To authenticate the User (on the main domain, say, example.com) -
signIn(email: string, password: string): Observable<any> {
let userPool = new CognitoUserPool({
UserPoolId: environment._USER_POOL_ID,
ClientId: environment._CLIENT_ID,
Storage: new CookieStorage({secure: false, domain: "example.com"}),
});
let authenticationDetails = new AuthenticationDetails({
Username: email,
Password: password,
});
let userData = {
Username: email,
Pool: userPool,
Storage: new CookieStorage({secure: false, domain: "example.com"}),
};
let cognitoUser = new CognitoUser(userData);
return Observable.create((observer: Observer<any>) => {
cognitoUser.authenticateUser(authenticationDetails, {
onSuccess: result => {
observer.next(result);
observer.complete();
},
onFailure: error => observer.error(error),
});
});
}
To check if user is authenticated (on a subdomain, say, sub.example.com)
isAuthenticated(): Observable<boolean> {
let userPool = new CognitoUserPool({
UserPoolId: environment._USER_POOL_ID,
ClientId: environment._CLIENT_ID,
Storage: new CookieStorage({secure: false, domain: "example.com"}),
});
let cognitoUser = userPool.getCurrentUser();
if (cognitoUser != null) {
return Observable.create((observer: Observer<boolean>) => {
cognitoUser.getSession((error, session) => {
if (error) {
console.error(error);
observer.next(false);
observer.complete();
}
console.log(session, session.isValid(), session.isAuthenticated);
observer.next(session.isValid());
observer.complete();
});
})
}
The tokens for the Amazon Cognito Identity SDK for JavaScript are stored in local storage indeed. However, if you use the Cognito Hosted UI and the Cognito Auth SDK linked below, you can manage SSO.
The way that works, if you have tokens stored against the local storage of your domain, you can login. Otherwise, you hit our login endpoint and if there is a cookie stored against it, you get tokens in the response that will be stored in local storage and can be accessed with the Amazon Cognito Identity SDK (cause they are stored in the same location).
https://github.com/aws/amazon-cognito-auth-js
Related
I am creating a Shiny app that involves AAD token authentication to connect to Snowflake.
I am using a .js script below that obtains the token:
async function wrapperFunc() {
const msalConfig = {
auth: {
clientId: 'XXXXXXXXX',
authority: 'https://login.microsoftonline.com/XXXXXXXXXXX'
}
};
const msalInstance = new msal.PublicClientApplication(msalConfig);
const silentRequest = {
scopes: ["api://XXXXXX/session:role-any"]
};
const callLogin = async function(silentRequest, msalInstance) {
try {
const loginResponse = await msalInstance.loginPopup(silentRequest);
return loginResponse;
} catch (err) {
console.log(err)
}
}
response = callLogin(silentRequest, msalInstance);
return response;
}
wrapperFunc().then(result => {
Shiny.setInputValue("oauthToken", result['accessToken']);
console.log(result['accessToken']);
});
and then plugging that token into the following db connection:
pii_db_connection <- function(OAuth_token) {
connection <- DBI::dbConnect(
drv = odbc::odbc(),
dsn = "snowflake",
token = OAuth_token,
authenticator = "oauth"
)
return(connection)
}
I am redirected to the browser window to log in, and then once that's done, I get hit with this error message:
AADSTS50011: The redirect URI 'XXXXX' specified in the request does not match the redirect URIs configured for the application 'XXXXX'. Make sure the redirect URI sent in the request matches one added to your application in the Azure portal.
I can access the app by changing the URL to a localhost URL. But my question is, can I get it to automatically redirect the browser window to the localhost URL?
I am logging users in via their domain Google accounts using passport.js. This works great, but now I need to give this application access to a few Google API's (drive, sheets, etc).
When a user logs in, a message appears in the logs, that makes it seem like passport has all the required info:
info: [06/Jun/2019:21:24:37 +0000] "302 GET /auth/callback?code=** USER ACCESS TOKEN HERE **&scope=email%20profile%20https://www.googleapis.com/auth/drive.file%20https://www.googleapis.com/auth/spreadsheets%20https://www.googleapis.com/auth/userinfo.email%20https://www.googleapis.com/auth/userinfo.profile%20https://www.googleapis.com/auth/drive HTTP/1.1" [46]
This is achieved by passing the appended scopes via passport.authenticate(), which presents the user with the "Grant access to these things on your Google account to this app?" screen :
//Initial auth call to Google
router.get('/',
passport.authenticate('google', {
hd: 'edmonds.wednet.edu',
scope: [
'email',
'profile',
'https://www.googleapis.com/auth/drive',
'https://www.googleapis.com/auth/drive.file',
'https://www.googleapis.com/auth/spreadsheets'
],
prompt: 'select_account'
})
);
However, when I go and try to call an API with something like:
const {google} = require('googleapis');
const sheets = google.sheets({version: 'v4', auth});
router.post('/gsCreate', function(req,res,next){
sheets.spreadsheets.create({
// Details here.....
});
});
I get nothing but errors (the current one is debug: authClient.request is not a function)
My question is: Is it possible for me to use a setup like this, asking the user to log in and grant permissions once, and then somehow save that to their user session via passport?
I had the same question, but I was able to access Google Gmail API functionalities along with Passport.js user authentication by specifying 'scopes' using the following process.
First, create a file to setup the passport-google-strategy in nodejs as follows.
passport_setup.js
const passport = require('passport')
const GoogleStrategy = require('passport-google-oauth20')
const fs = require("fs");
const path = require('path');
//make OAuth2 Credentials file using Google Developer console and download it(credentials.json)
//replace the 'web' using 'installed' in the file downloaded
var pathToJson = path.resolve(__dirname, './credentials.json');
const config = JSON.parse(fs.readFileSync(pathToJson));
passport.serializeUser((user, done) => {
done(null, user.id)
})
passport.deserializeUser((id, done) => {
const query = { _id: id }
Users.findOne(query, (err, user) => {
if (err) {
res.status(500).json(err);
} else {
done(null, user)
}
})
})
//create a google startergy including following details
passport.use(
new GoogleStrategy({
clientID: config.installed.client_id,
clientSecret: config.installed.client_secret,
callbackURL: config.installed.redirect_uris[0]
}, (accessToken, refreshToken,otherTokenDetails, user, done) => {
//in here you can access all token details to given API scope
//and i have created file from that details
let tokens = {
access_token: accessToken,
refresh_token: refreshToken,
scope: otherTokenDetails.scope,
token_type: otherTokenDetails.token_type,
expiry_date:otherTokenDetails.expires_in
}
let data = JSON.stringify(tokens);
fs.writeFileSync('./tokens.json', data);
//you will get a "user" object which will include the google id, name details,
//email etc, using that details you can do persist user data in your DB or can check
//whether the user already exists
//after persisting user data to a DB call done
//better to use your DB user objects in the done method
done(null, user)
})
)
Then create your index.js file in nodejs for API route management and to call send method of Gmail API.
Also, run the following command to install "google-apis"
npm install googleapis#39 --save
index.js
const express = require("express")
//import passport_setup.js
const passportSetup = require('./passport_setup')
const cookieSeesion = require('cookie-session');
const passport = require("passport");
//import google api
const { google } = require('googleapis');
//read credentials file you obtained from google developer console
const fs = require("fs");
const path = require('path');
var pathToJson_1 = path.resolve(__dirname, './credentials.json');
const credentials = JSON.parse(fs.readFileSync(pathToJson_1));
//get Express functionalities to app
const app = express();
// **Middleware Operations**//
//cookie encryption
app.use(cookieSeesion({
name:'Reserve It',
maxAge: 1*60*60*1000,
keys: ['ranmalc6h12o6dewage']
}))
//initialize passort session handling
app.use(passport.initialize())
app.use(passport.session())
app.use(express.json());
//**API urls**//
//route to authenticate users using google by calling google stratergy in passport_setup.js
//mention access levels of API you want in the scope
app.get("/google", passport.authenticate('google', {
scope: ['profile',
'email',
'https://mail.google.com/'
],
accessType: 'offline',
prompt: 'consent'
}))
//redirected route after obtaining 'code' from user authentication with API scopes
app.get("/google/redirect", passport.authenticate('google'), (req, res) => {
try {
//read token file you saved earlier in passport_setup.js
var pathToJson_2 = path.resolve(__dirname, './tokens.json');
//get tokens to details to object
const tokens = JSON.parse(fs.readFileSync(pathToJson_2));
//extract credential details
const { client_secret, client_id, redirect_uris } = credentials.installed
//make OAuth2 object
const oAuth2Client = new google.auth.OAuth2(client_id,
client_secret,
redirect_uris[0])
// set token details to OAuth2 object
oAuth2Client.setCredentials(tokens)
//create gmail object to call APIs
const gmail = google.gmail({ version: 'v1', auth: oAuth2Client })
//call gmail APIs message send method
gmail.users.messages.send({
userId: 'me',//'me' indicate current logged in user id
resource: {
raw: //<email content>
}
}, (err, res) => {
if (err) {
console.log('The API returned an error: ' + err)
throw err
}
console.log('Email Status : ' + res.status)
console.log('Email Status Text : ' + res.statusText)
})
res.status(200).json({ status:true })
} catch (err) {
res.status(500).json(err)
}
})
app.listen(3000, () => { console.log('Server Satrted at port 3000') })
You can separate the routes in the index.js file to different files for clarity using express.Router()
If you want to call another Google API service just change this code segment and code below that;
const gmail = google.gmail({ version: 'v1', auth: oAuth2Client })
gmail.users.messages.send(....Send Method internal implementation given above....)
For Google Drive:
const drive = google.drive({version: 'v3', auth: oAuth2Client});
drive.files.list(...Refer "Google Drive API" documentation for more details....)
I believe you can't use passport.js for three-legged oauth for APIs like Sheets or Drive.
Have a look at the Using OAuth for web servers documentation instead.
user835611 has the correct answer, as that page explains everything quite nicely. However, if you still need more, the below link really helped me to understand how this works.
https://github.com/googleapis/google-auth-library-nodejs#oauth2
I'm trying to set my application up so I can use the Spotify API. Their API requires an Authorization token along with every request, and this token is different every user session. I have successfully implemented OAuth2 login using the 'passport-spotify' module (detail below) and have the token, which I am currently storing in my database. Once it is in the database, it is also available in my Redux store.
Functioning Passport strategy:
const spotifyConfig = {
clientID: process.env.SPOTIFY_CLIENT_ID,
clientSecret: process.env.SPOTIFY_CLIENT_SECRET,
callbackURL: process.env.SPOTIFY_CALLBACK
}
const strategy = new SpotifyStrategy(spotifyConfig, (accessToken, refreshToken, profile, done) => {
const spotifyId = profile.id
const name = profile.displayName
const email = profile.emails[0].value
User.find({where: {spotifyId}})
.then(foundUser => (foundUser
? foundUser.update({accessToken, refreshToken}).then(() => done(null, foundUser))
: User.create({name, email, spotifyId, accessToken, refreshToken})
.then(createdUser => done(null, createdUser))
))
.catch(done)
})
passport.use(strategy)
router.get('/', passport.authenticate('spotify', {scope: ['user-read-email'], showDialog: true}))
router.get('/callback', passport.authenticate('spotify', {
successRedirect: '/home',
failureRedirect: '/login'
}))
What I am currently stuck on is how to set up my API requests so it accesses that token on every call. The 'spotify-web-api-node' node module has a setCredentials method, but I can't figure out how to access the token.
Semi-functioning API call (it makes the API request but gives me a 403 unauthorized):
const SpotifyWebApi = require('spotify-web-api-node');
const spotifyApi = new SpotifyWebApi();
spotifyApi.setCredentials({
clientId: 'my client id',
clientSecret: 'my client secret',
redirectUri: 'http://localhost:8888/auth/spotify/callback',
refreshToken: 'cant figure out how to properly include this',
accessToken: 'and this.',
});
export function searchMetallica(){
return spotifyApi.searchArtists('Metallica')
.then(function(data) {
console.log(data.body);
}, function(err) {
console.error(err);
});
}
I hope this isn't too much a of a newbie question. Thanks in advance.
You're very close!
You need to pass the token to the setAccessToken() method on the Spotify wrapper, in your case:
spotifyApi.setAccessToken(<youraccesstoken>);
You can set the refresh token in a predictably similar way:
spotifyApi.setRefreshToken(<yourrefreshtoken>);
Easy peasy!
But, there's a catch. If you use this spotifyApi for all your calls, this will set the same access token for all those calls! You need to make sure to use the appropriate access token for each user, so user A can't do actions for user B and vice versa.
You can get around this by simply instantiating the API wrapper and setting the access token at the point when the user logs in, or at the point when the call is made. For example, a call to get top tracks may look like (using Express for convenience):
app.get('/myendpoint', function (request, response) {
const loggedInSpotifyApi = new SpotifyWebApi();
loggedInSpotifyApi.setAccessToken(request.access_token);
// Get top tracks!
loggedInSpotifyApi.getMyTopTracks()
.then(function(data) {
response.send(data.body);
}, function(err) {
console.error(err);
});
});
Here's a full working Glitch that shows the Authorization Code flow and spotify-web-api-node: https://glitch.com/edit/#!/spotify-authorization-code
Let me know if you have any questions!
My setup consists of an AWS API Gateway with IAM access control and AWS cognito for log in.
I access the API already from an Android app and would now like to build a web app (angular2) to do the same.
On Android, I'm using the AWSCognitoCredentialsProvider to supply the API SDK with the required credential. (http://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-generate-sdk.html)
Unfortunately I cannot figure how / if I can do that with the javascript SDK?
I have no trouble using cognito to log in and get the session ID, access token etc. However, the API SDK requires me to provide accessKey and secretKey.
Here's the relevant code snippet from the generated API SDK:
var authType = 'NONE';
if (sigV4ClientConfig.accessKey !== undefined && sigV4ClientConfig.accessKey !== '' && sigV4ClientConfig.secretKey !== undefined && sigV4ClientConfig.secretKey !== '') {
authType = 'AWS_IAM';
}
In other words, I have this part working (from some example code):
static authenticate(username:string, password:string, callback:CognitoCallback) {
AWSCognito.config.update({accessKeyId: 'anything', secretAccessKey: 'anything'})
let authenticationData = {
Username: username,
Password: password,
};
let authenticationDetails = new AWSCognito.CognitoIdentityServiceProvider.AuthenticationDetails(authenticationData);
let userData = {
Username: username,
Pool: CognitoUtil.getUserPool()
};
console.log("Authenticating the user");
let cognitoUser = new AWSCognito.CognitoIdentityServiceProvider.CognitoUser(userData);
console.log(AWS.config);
cognitoUser.authenticateUser(authenticationDetails, {
onSuccess: function (result) {
callback.cognitoCallback(null, result);
},
onFailure: function (err) {
callback.cognitoCallback(err.message, null);
},
});
}
and now I'd like to use this:
this.apigClient = apigClientFactory.newClient({
accessKey: "anything",
secretAccessKey: "anything",
sessionToken: "nothing",
region: 'eu-central-1'
How do I get accessKey, secretAccessKey and sessionToken out of my AWSCognito? I was unable to find any API for that so far...
Thank you Bob, for pointing me in the right direction! I've now figured it out and thus for completeness sake, here's the full solution to my problem:
From the service that creates the apigClient:
return CognitoUtil.getCredentials()
.then(() =>
this.apigClient = apigClientFactory.newClient({
accessKey: AWS.config.credentials.accessKeyId,
secretKey: AWS.config.credentials.secretAccessKey,
sessionToken: AWS.config.credentials.sessionToken,
region: 'eu-central-1'}));
The getCredentials() method, which is key to get the required temporary credentials:
public static getCredentials():Promise{
return new Promise((resolve, reject) => {
CognitoUtil.getIdToken({
callback() {
},
callbackWithParam(idTokenJwt:any) {
let url = 'cognito-idp.' + CognitoUtil._REGION.toLowerCase() + '.amazonaws.com/' + CognitoUtil._USER_POOL_ID;
let logins = {};
logins[url] = idTokenJwt;
let params = {
IdentityPoolId: CognitoUtil._IDENTITY_POOL_ID, /* required */
Logins: logins
};
AWS.config.region = CognitoUtil._REGION;
AWS.config.credentials = new AWS.CognitoIdentityCredentials(params);
AWS.config.credentials.refresh(result => {
console.log(AWS.config.credentials);
resolve();
});
}
});
});
}
So the key insight here was, that
I authenticate to the user pool (shown in my question)
I use that with an identity provider to retrieve temporary credentials (getCredentials)
I use the temporary credentials out of AWS.config.credentials to setup the apigClient
I hope this is helpful to someone else as well. Certainly the code that I just posted probably could use some refactoring, so any comments on that are very welcome!
Cognito is actually made of 3 different services:
Cognito Your User Pools - What you've integrated here
Cognito Sync - For syncing user preference data for users
Cognito Federated Identity - For federating identities (FB, Google or User Pools) into your account and generating credentials.
What the API Gateway client is expecting is credentials that come from Cognito Federated Identity.
See the Cognito documentation for integrating your user pool with Cognito Federated Identity.
I've created a Cognito User Pool. I can list the users and add the users using the AWSCognitoIdentityProviderClient from the Java AWS SDK.
However, I have a custom login page and I wish to take the entered username and password and authenticate against my User Pool. I don't see anywhere in the Java AWS SDK where I can pass credentials and get an authentication result from.
Edit: I can't get past this error:
NotAuthorizedException: Missing credentials in config
Relevant code:
AWS.config.region = 'us-east-1';
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: 'us-east-1:087a3210-64f8-4dae-9e3c...' // your identity pool id here
});
AWSCognito.config.region = 'us-east-1';
AWSCognito.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: 'us-east-1:087a3210-64f8-4dae-9e3c...' // your identity pool id here
});
var poolData = {
UserPoolId: 'us-east-1_39RP...',
ClientId: 'ttsj9j5...',
ClientSecret: 'bkvkj9r8kl2ujrlu41c7krsb6r7nub2kb260gj3mgi...'
};
var userPool = new AWSCognito.CognitoIdentityServiceProvider.CognitoUserPool(poolData);
var authenticationData = {
Username: 'test#foo.com',
Password: 'foobarfoo',
};
var authenticationDetails = new AWSCognito.CognitoIdentityServiceProvider.AuthenticationDetails(authenticationData);
var userData = {
Username: 'test#foo.com',
Pool: userPool
};
var cognitoUser = new AWSCognito.CognitoIdentityServiceProvider.CognitoUser(userData);
cognitoUser.authenticateUser(authenticationDetails, {
onSuccess: function (result) {
console.log('access token + ' + result.getAccessToken().getJwtToken());
},
onFailure: function (err) {
alert(err);
},
});
The AWS Java SDK includes APIs to authenticate users in a User Pool. You can authenticate a user using either the InitiateAuth api or AdminInitiateAuth api of the AWSCognitoIdentityProviderClient class. The difference between these two API is explained in the documentation. In short, for InitiateAuth, you need to perform SRP calculations and then pass it to the API, while in AdminInitiateAuth you can directly pass the username and password. You can read about the security implications in both cases and decide which one to use.
Documentation :
https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-authentication-flow.html
API reference:
https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_InitiateAuth.html
https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_AdminInitiateAuth.html
My working sample(Groovy):
def login() {
AWSCognitoIdentityProviderClient client = new AWSCognitoIdentityProviderClient()
println("Provider client: " + client)
client.setRegion(Region.getRegion(Regions.AP_NORTHEAST_1))
HashMap authParams = new HashMap<>()
authParams.put("USERNAME", "User1")
authParams.put("PASSWORD", "a*123")
AdminInitiateAuthRequest adminInitiateAuthRequest = new AdminInitiateAuthRequest()
.withClientId(<YOUR_CLIENT_ID>)
.withUserPoolId(<YOUR_USER_POOL_ID>)
.withAuthFlow(AuthFlowType.ADMIN_NO_SRP_AUTH )
.withAuthParameters(authParams)
AdminInitiateAuthResult result = client.adminInitiateAuth(adminInitiateAuthRequest);
if (result != null) {
System.out.println("AdminInitiateAuthResult:");
System.out.println(result.toString());
} else {
System.out.println("No result available");
return;
}
}
Authentication is only supported via JavaScript, iOS and Android at this time. The necessary apis to authenticate are not part of the server SDKs (java, python et. all) during the beta. Using the JavaScript SDK is the recommended way of authenticating from your login page.
check here https://github.com/aws/amazon-cognito-identity-js
there is a missing line of code
This page http://docs.aws.amazon.com/cognito/latest/developerguide/using-amazon-cognito-user-identity-pools-javascript-examples.html is not updated
// Need to provide placeholder keys unless unauthorised user access is enabled for user pool
AWSCognito.config.update({accessKeyId: 'anything', secretAccessKey: 'anything'})
After including this I stopped having this error.