AWS Cognito custom auth - sending metadata to a challenge lambda functions - amazon-web-services

I'm developing a custom passwordless auth to sign into a Cognito user pool. I will describe what I'm trying to implement in case anything is silly. I want a user to enter their email address, then receive a magic login link via email, and when they click on that be taken back to the site and be logged in.
This uses custom auth lambda functions to define/create a challenge with a time based password and send it to the user in an email. I am having a couple of problems:
Problem 1)
When the user returns with the code they might not be in the same browser/device and certainly won't be in the same tab so they don't have the session, meaning I need to call cognitoUser.initiateAuth again. This goes through the define/create challenge lambdas again so a second email gets sent even though at this point the user is coming from the email link so already has the code. Note: the session id is not available in the event object when the challenge is created, also I've read these sessions only last 3 minutes and my time based passwords will last ~15minutes, so I don't think I can include the session id in the email.
Problem 2)
You can login from a few places (browser, android app, etc) and I would like to be able to include the url or at least protocol as a parameter to control what gets sent in the email, e.g. if you entered your email address in the android app then the email you get would be myapp://login?code=xxx and if you did it on the web it would be https://example.com/login?code=xxx
It seems like I would be able to implement both of these to work properly if only I could find some way to send custom metadata through to the DefineChallenge and CreateChallenge lambda such that it would appear in the event object. I thought adding ValidationData to the AuthenticationDetails object would do this, but that information doesn't appear in the event object in the Lambda fns.
The workaround I've found is to create a new client id for every situation - one for initiating auth, one for redeeming token, and repeat for each different protocol. But that is a lot of client ids quickly - a pain to mantain and clumsy.
So tl;dr is: I want to send custom metadata from my cognitoUser.initiateAuth(...) call in JS and have it available in my Define/Create Challenge lambda fns.

You can split the authentication process into multiple custom auth challenges. This allows custom auth state to be supplied via the challenge response as client metadata.
Auth session state must be persisted in a database in order to be shared between devices.
Your custom login flow will probably have two challenge steps: the first prompts for auth type and the second prompts for the secret code. The action taken by the "Create Auth Challenge" Lambda will depend on the auth type. If the auth type is "Email" then the secret code and magic link are generated, stored in DynamoDB and emailed. If the auth type is "MagicLink" then the secret is loaded from DynamoDB. Clicking on a Magic link will initiate a new auth session and automatically supply all the challenge answers.
There are a few other things to consider:
Your magic link needs to encapsulate the Cognito username as well as the one-time secret and probably some other session id that is used as a key in dynamodb.
You probably should not put app-specific links into your emails. Instead associate your domain with your app and/or leverage the redirect URI parameter of your web-based login page.
You can also access custom Cognito user attributes from the Lambda function which can be used to indicate user login preferences (eg Email vs SMS for login codes).

Related

Tronweb authentication

I want to build a webapp that uses the wallet address as account, but yet I want to store the user in my db to allow specifying a nickname.
The problem I'm dealing with is that I want to call different apis, where the user needs to be authenticated / authorized in order to gain access .
Since the authentication happens 100% client side in my case (through the tronlink extension and TronWeb), I don't see another way as to add supplementary authentication for my webapp specifically.
If the user logs in to my app with a different password (not using the private key of the wallet), it seems like bad user experience as the user needs to authenticate twice (through Tronweb AND my webapp with a password).
How do you solve this problem?
It seems that the way to handle this is to foresee a separate login flow for the web app after all.
Even when the user already has logged in into Tronlink, it needs to obtain a token to authenticate rest calls.
The way it would appear to work is by generating a random nonce and storing this nonce along with the public key in the User table.
The login flow then consists of signing the nonce in the front-end, and verifying the signature in the backend after which the token will be generated and returned.

AWS Cognito - run another lambda after migration lambda has run

Cognito has a migration lambda that allows us to confirm a user in our db. They send the email and PW to Cognito, the lambda fires, we verify matches, and the user is entered into Cognito.
At this point - behind the scenes - Cognito generates a username of some kind (UUID). The problem is, I need a way to get this username into our existing database, because our systems going forward will no longer rely on email and instead rely on this username.
Ideal flow:
Sign In
Migration Succeeds
Cognito generates username
Username is sent to our server.
Now because we have email set to auto-verified, no post-confirmation lambda can be called. The only way I see to do this with Cognito as-is is to either:
Ask users who already exist in our system to confirm their email again. This is a non-starter
Create a post-auth lambda, check user login count through a custom attribute, and if 0 (or if not already registered with the service, etc.) migrate the username to the new service.
If there is any other way to do this, please let me know.
After the user migration lambda is called your pre sign-up lambda will be called, assuming you have implemented it. The parameters received by your lambda will include username with the value being the UID you referenced. Parameters will also include user attributes containing email. You can use this information to update your database.
I did not want to add the PreSignup trigger, its a complicated way of doing it if you already rely on PostConfirmation, and if the majority of new users won't be migrations. My use case has a frontend initiate the signup process as well, which I use here.
Instead, I set a Cognito attribute on the new user during the UserMigration trigger. It could be 'user_migration': <oldUserSub>, or however you want to mark it. Just make sure you allow this property within the Cognito user pool settings.
When the UserMigration trigger returns, this information is now accessible through verifying the IdToken, or found in the JWT on the frontend if you're using that. So, when the user is migrated into Cognito and the response gets back to the Cognito client on the frontend, I can now recognize this user needs to be migrated into my personal database. Seeing this, I'll call a new endpoint on my backend to handle this. This new endpoint does exactly what PostConfirmation would typically do.
Then just delete the 'user_migration' property from the Cognito user, return the new user data to the frontend and everything should be set up.
You can use Pre sign-up trigger. In order to detect if the trigger event came from your migration trigger, you can check at the trigger_source value from the event object. In my case (i'm using migration trigger) the value is PreSignUp_AdminCreateUser. By knowing the value of trigger_source you can differentiate if it was migrated or regular user. You can also check the user attributes to know whether the email or phone is verified or not.
Here's my sample code on python:
def lambda_handler(event, context):
trigger_source = event.get('triggerSource')
user_attributes = request.get('userAttributes')
email_verified = user_attributes.get('email_verified')
if trigger_source == 'PreSignUp_AdminCreateUser' and email_verified == 'true':
# create user on db

Cognito User Pools - Is it possible to create a custom sign up/in form for Facebook login?

I would like to use a Cognito User Pool for Facebook logins only, which may be possible using the built in login form, but I need to use my own.
Theoretically, when it comes to a custom form, it shouldn't be hard: after I receive a user object from FB, I bind the user and email attributes to the ones in my User Pool and I save it.
But what to do about the password field and future authentication? And here I have failed during my journey...
...
userPool.signUp('FoobarUser', '**password?**', attributeList, null, function(err, result){
...
While digging deeper into the docs, I tried to implement a Identity Pool (Federated Identities), managed to save user info in the form of datasets as well, but then I realised, querying these sets gonna be a huge pain if possible at all.
Maybe I'm failing to understand the concepts, I would be really thankful if someone could suggest a way to manage facebook logins in a nicely organised fashion using Cognito.
So here is what I understand from your query.
Setup
Link Facebook to userpool
The app client allows only Facebook login, no other providers allowed (not even Userpool)
On login, you want Facebook user's info to be automatically populated in your Userpool
You don't want to use Cognito's builtin UI but use your own
My 2 Cents
In your app client, just select Facebook
In your UI, have a login button. On clicking it should redirect to your userpool's authorization endpoint
https://your_domain.auth.us-east-1.amazoncognito.com/oauth2/authorize?redirect_uri=https://www.example.com&response_type=token&client_id=your_appclient_id
If you want to use your own UI with multiple providers, allow the same in Client and on clicking the appropriate button in your UI (say Facebook), redirect users to the authorize endpoint but append the identity_provider in the URL
https://your_domain.auth.us-east-1.amazoncognito.com/oauth2/authorize?redirect_uri=https://www.example.com&response_type=token&client_id=your_appclient_id&identity_provider=Facebook
If you want to see the names of all supported identity providers, use ListIdentityProviders API call
This way, all Facebook users will be automatically created in your userpool. Of course, their names will be random like Facebook_123jkjdwj but all their details will be correctly populated from the token as per your attribute mapping. As a plus, all auto-created users from a particular provider are added to an auto-created group 'Userpoolid_providername' eg. us_east_1_xxxx_Facebook.

Mirror API send timeline item to particular user

I need to send timeline item to particular subscribed user using Mirror API. I have the user's email id. How can I achive this?
Thanks
Update:
I have GDK app, companion app(which runs on Android mobile device) and Mirror API app. Both GDK app and companion paired via Bluetooth. My use case is I have to send timeline item to uesr if he reached particular location. We are using ibeacon to check user's location. When user reached that particular area, companion app detect it(via bluetooth) and send request to mirror app then mirror app will add timeline item to user's glass. Here my question is how to add the timeline item to one particular user?(not to all subscribed users) And what parameter should I pass to mirror app from companion app?(I was thinking to send the user's email id)
The user will have needed to log into your service using OAuth2 and have granted specific permission for you to access their timeline using the role https://www.googleapis.com/auth/glass.timeline. You should request "offline" access so you will receive both an auth token and a refresh token, which you can use to get a new auth token after an hour.
You will need this auth token when you send a card to the timeline, which also serves as an identifier in this case. Having their email id is not enough, and you don't need it.
See https://developers.google.com/glass/develop/mirror/authorization for some code samples and details.
Update:
So it sounds like you have the following overall work flow:
User creates an account on your website (which is where the Mirror API app is hosted). As part of this, they authorize access to their Glass and either give you their email address or authorize you to get it via Google's API.
You'll store this information (auth_token and refresh_token) in a data store somewhere, indexed against their email address.
They will also install your app on their phone, and it has access to the email address as well.
When the mobile app detects an ibeacon marker it is interested in, it connects to your web service and sends the email address and location.
Your web service looks up the email address, gets the access token to authenticate the connection to the Mirror service, and sends a message to Glass with the location information.
This is a generally reasonable workflow, but there are a couple of important points to make:
The Mirror API is well tuned to sending things to just one person at a time. You sound worried about sending bulk results, but as long as you use the auth token for just one user, it will send it to just that user.
You're using the email address as an index to the entire user account. While this is simple to implement, this is not the best solution, since it means that anyone who has a person's email address and the URL for the endpoint of your service can fake locations. You may consider this an acceptable risk given how you're using the location information (sending it back to the user), but you need to think about how the service could be misused.
You can mitigate the risk in a couple of potential ways:
Instead of an easily guessable email address, you can create and use some other userid which the user will need to enter when they first setup the companion app.
The first time (and only the first time) the app wants to connect to the service, it creates and sends a random secret string which it will use as a password and the web service could store this random string. Afterwards, the companion app would need to send this string along with the email address.
Depending on your needs, you could cut out the webapp completely and have the companion app use the Mirror API directly. This would leave the auth tokens on the phone and would greatly reduce the potential chance to have someone spoof your user. It does have a significant downside - although you can use it to send cards to Glass, it becomes more difficult to get responses from Glass back to the companion device.
As I understand your question and comments above, your user has already authenticated with your Mirror API based application, so you already have the required credentials (auth/refresh tokens). Your companion Android application detects a condition (user in a particular area) and sends a request to your remote endpoint in your Mirror API based application.
The companion app, when sending the request to the remote endpoint, needs to send a common piece of information that can be used to identify that user in your Mirror API app. In this case, you're saying you're sending the users email id.
To send a timeline card to only that particular user, I would take the email id that the companion application has sent, query your database to return the credentials that you saved when the user authenticated originally with your Mirror API based app and then use that to create an authenticated Mirror API request that inserts the timeline item for only that user. I don't know what your Mirror API app is written in, but a basic example in Python might take the following form:
# You sent along the email address
userid = notification['MyCompEmailId']
# set timeline card body
timelinecard_body = {
'notification': {'level': 'DEFAULT'},
'text': "You found a beacon!",
'menuItems': [{'action': 'DELETE'}]
}
# Look up the user in our database and
# get their credentials
#
# _credentials_for_user() basically does a "WHERE userid = 'something'" query
user_credentials = _credentials_for_user(userid).get()
# Create a Mirror API service with some credentials.
authed_mirror_service = build('mirror', 'v1', http=user_credentials.authorize(httplib2.Http()))
# Send a timeline card
authed_mirror_service.timeline().insert(body=timelinecard_body).execute()

Securing a REST API with Facebook OAuth

I am building a app/API that allows user to login with Facebook, Twitter or Google. I am wondering what are the best practices in allowing those user to use the same account to login to the API.
A couple Ideas that I have had is pass the auth token/cookie in a header to the API for every request and use that to authenticate on the backend.
Run my own OAuth setup and make the user authenticate once with the back end to get my OAuth token and use those from then on.
I am doing the same thing and my solution is to match the email addresses that you get from these respective APIs.
For Facebook, you need special permission from the end user to get the email address registered there. You do this by adding &scope=email to the first oauth request.
A disadvantage is that you need to get this permission from the end user and they may decline. Another disadvantage is that users need to use the same email addresses for Google, Facebook and Twitter.
An advantage is that user records are merged automatically, so users can directly access all their data if they logged in the first time through Google, and the second time through Facebook.
Another approach would be to manually merge their data by making them log in to Google when they are already logged in through Facebook. Then you can conclude that they are the same user, even when they use different email addresses for both. But this is a more tedious approach, as you still need to merge the app's user data from both accounts.
Your first solution is exactly the way I do it. As all my rest services are stateless, the access token goes in the header and is parsed by spring security authentication filters on every request. I use a grails sever with the spring-security-oauth plugin. We also run a website which allows for using session cookies for browser based access.