AWS Cognito and Amplify: clientMetadata not sent when session is refreshed - javascript

We have a React client that uses AWS Cognito and Amplify ("aws-amplify": "1.1.40").
When a user logs in we want to send some additional data to Cognito, to be used by a "pre token generation" trigger. We do this by adding a clientMetadata ({"metadataKey1": "metadataValue1"}) object to the Auth.signIn function call:
Auth.signIn(auth.email, auth.password, {"metadataKey1": "metadataValue1"})
.then(response => {
// Sign in OK
})
.catch(error => {
// Something went wrong
});
This works as expected, and the Cognito "pre token generation" lambda can extract the "metadataKey1" from the clientMetadata in the request.
This is where our problem starts:
After the successful signIn, AWS Amplify automatically does a session refresh. This session refresh is not explicitly done by our code, and the clientMetadata object used during signIn is not set. This of course means that the automatic session refresh request to Cognito does not contain the clientMetadata, which in turn means that the Cognito "pre token generation" lambda can not extract "metadataKey1" from the clientMetadata in the request (as it does not exist).
We have debugged the code, and found that the automatic request to Cognito happens in #aws-amplify\auth\node_modules\amazon-cognito-identity-js\es\CognitoUser.js#1249, CognitoUser.prototype.refreshSession. The refreshSession function can receive a clientMetadata object, but when debugging the code the clientMetadata object is always undefined (which makes sense; we have not set it explicitly and the Amplify code does not seem to store/use the clientMetadata we set during signIn).
What we need help with:
Are we not doing enough? Do we have to do some other things in our code to make sure the clientMetadata object is sent on every request to Cognito, even requests that are not explicitly done by our code?
Are we doing it wrong? The goal is to make sure we can send our own data on every request to Cognito. Are there other ways to do this than use the clientMetadata object?
Would really appreciate any help with this!

According to API Reference, The ClientMetadata value is passed as input to the functions for ONLY the following triggers:
Pre signup
Pre authentication
User migration

Related

apple-signin-auth node js react native error: Invalid id token public key id

I have a React native front end where I use invertase/react-native-apple-authentication to handle Apple Authentication.
Then I have a NodeJS back end, where I use A-Tokyo/apple-signin-auth to handle Apple authenticated users and let them access routes.
I made this authentication based on this article.
I want the users to be able use the app without logging in again without a time limit.
Therefore I save the identity token, which I get when the user does the first sign up in Async Storage in the front-end. Every time the user tries to access routes the user will be checked if he/she has a identityToken in the Header in my isAuth middleware in the NodeJS backend for the respective request.
I can see in my logs not sometimes requests get the following error the backend in my isAuth middleware:
JsonWebTokenError: error in secret or public key callback: input error: Invalid id token public key id at /app/node_modules/jsonwebtoken/verify.js:96:19 at _getIdTokenApplePublicKey (/app/node_modules/apple-signin-auth/lib/index.js:1:5730) at runMicrotasks () at processTicksAndRejections (internal/process/task_queues.js:95:5)
The error is thrown in the apple-signin-auth library when executing this code:
const appleSignin = require("apple-signin-auth");
result = await appleSignin.verifyIdToken(token, {
audience: config.CLIENT_ID_APPLE,
ignoreExpiration: true, // ignore token expiry (never expires)
});
I am not sure why this is happening. When I check the tokens, they seem fine but expired. The CLIENT_ID is right 100%. Does it has something todo with the expiration of the token?
A list for valid options for the appleSignin.verifyIdToken function is stated here.
Thanks for the help!
What I found out is that the public keys from the apple endpoint: https://appleid.apple.com/auth/keys
are changing over time. The library is using those keys for decoding or validating the identityToken. As a result the key ids are not matching with the key id from the identity token I saved in the Async Storage. It cannot find the matching kid in the public keys because they are not there anymore. I am thinking about what solution to implement.
Possible solution: Instead of storing the IdentityToken, I should store the RefreshToken in the AsyncStorage in the front end. Hopefully that's a valid approach. (Token based authentication)
And then request a new IdentityToken with every back-end request in the isAuth Middleware with the RefreshToken (which according to Apple is endlessly valid).
Then verify the IdentityToken with the
appleSignin.verifyIdToken
method of apple-signin-auth package and give user access to route or not. The public keys from the Apple endpoint are always up-to-date, since the identity token is always requested again.
As far as I understand the workflow, you verify the identity token in the backend only once when the user has authenticated themselves using "Sign in with Apple" on the device.
If verifying the identity token in the backend was successful, you receive a refresh token in the response. You are then supposed to save this refresh token in your backend and verify the refresh token once a day to check if the user is still logged in with Apple. What does that mean? For example a user could revoke access to your app. Or a different user could log in on the Apple device.
By the way, if you verify the refresh token on every request (read multiple times a day), you risk Apple throttling these requests.
Bear in mind that this doesn't free your system from rolling its own session management meaning that your system sends its own session ids back and forth between the backend and front-end. Once a day, you check the refresh token associated with a session to see if the user is still logged in.
Disclaimer: This is how I understood the docs of Sign in with Apple. In other works, I have no experience implementing it. Hope it helps nonetheless.

How to generate new id tokens for custom backend with firebase auth?

We have built a custom nodejs backend but the authentication is using firebase auth with idtoken, the idtokens expire after 1 hour and the user is automatically logged out. When using firestore this is handled automatically, we have seen solutions that suggest a service worker but that has not worked.
Can someone please suggest a stable solution for this may be a middleware on the backend API's that can regenerate the tokens?
Thanks
The user is not logged out and that is why Firestore keeps working. You can use getIdToken() method again to get user's ID Token and then pass it in API request.
firebase.auth().currentUser.getIdToken(/* forceRefresh */ true).then(function(idToken) {
// Send token to your backend via HTTPS
// ...
}).catch(function(error) {
// Handle error
});
The normal approach (which the Firebase services themselves use too) is to always the current token with each request to the backend service, so that the service has at least 5m to complete the request (which is a lot more than most services need).
If you need a token that can be used for longer, you can consider forcing a refresh of the token before you call the service as Dharmaraj pointed out in their answer.
Alternative, you can switch to using session cookies for the user, which can have an expiration of up to two weeks.

Unable to get CognitoIdentityId from Java Lambda Function

I currently have an API Gateway setup to tie in Lambda functions which are written using Java. I'm using the javascript SDK to call the API Gateway endpoints and they are correctly relaying to the Lambda functions. However when I try to access the Cognito Identity ID via:
context.getIdentity()
I get the following response:
lambdainternal.api.LambdaCognitoIdentity#xxxxxx
If I run
context.getIdentity().getIdentityId()
It just returns a empty string in the logger. Not sure what I need to do to get the Identity ID for the user making the request to pass through to the context.
I'm generating the Javascript AWS Api Client via the following:
var apiClient = apigClientFactory.newClient({
accessKey: credentials.accessKeyId,
secretKey: credentials.secretAccessKey,
sessionToken: credentials.sessionToken,
region: 'us-east-1'
});
The lambda functions are secured using IAM policies and the user is able to access the lambda functions so authentication is working correctly. It's just not providing the IdentityId for some reason.
In order for Lambda to access the Cognito identity in its own context variable, you need to enable the checkbox "Invoke with caller credentials" on the Integration Response tab.
Alternatively, you could also use API Gateway's mapping templates and pass the value of $context.identity.cognitoIdentityId to your Lambda function. The mapping template would look something like this:
{
"cognito-identity" : "$context.identity.cognitoIdentityId"
}
Please also have a look at one of API Gateway's forum discussions for more details / background information (How to pass cognito identity id to backend through API Gateway).
Try using event.requestContext.authorizer.claims. You have access to cognito:username, email and other stuff that gets crammed in the token when it's generated. You can even customize this with Cognito's pre token generation trigger.

AWS: Error when calling getCredentialsForIdentity()

I am trying to get a login procedure to work in AWS by following the Enhanced Authflow for Developer Authentication mentioned in the official documentation.
The code in both client and server are using JavaScript, and I'm using Node.js on the server-side.
I have managed to get the OpenId token back to the client but when I try to exchange it for credentials by calling getCredentialsForIdentity(), the client receives this error:
NotAuthorizedException: Access to Identity 'eu-west-1:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' is forbidden.
Also on the server-side when I print the variable holding my AWS.CognitoIdentity object and check the property cognitoidentity.config.credentialProvider all I get is:
{"providers":[null,null,null,null]}
It feels like the named custom developer provider I have associated with my identity pool should be in there but I seem to not be able to get it there.
Any thoughts on where I might have gone wrong?
That error is usually thrown when you try to get access to an authenticated id without providing any of the tokens linked to that identity. If it's an authenticated identity, make sure you are giving a token for it.

Oauth2 Implicit Flow with single-page-app refreshing access tokens

I am using Thinktecture AuthorizationServer (AS) and it is working great.
I would like to write a native javascript single page app which can call a WebAPI directly, however implicit flow does not provide a refresh token.
If an AJAX call is made, if the token has expired the API will send a redirect to the login page, since the data is using dynamic popups it will this will interrupt the user.
How does Facebook or Stackoverflow do this and still allow the javascript running on the page to call the APIs?
Proposed Solution
Does the below scenario sound sensible (assuming this can be done with iframes):
My SPA directs me to the AS and I obtain a token by Implicit Flow. Within AS I click allow Read data scope, and click Remember decision, then Allow button.
Since I have clicked Remember decision button, whenever I hit AS for a token, a new token is passed back automatically without me needing to sign in ( I can see FedAuth cookie which is remembering my decision and believe this is enabling this to just work).
With my SPA (untrusted app), I don't have a refresh-token only an access token. So instead I:
Ensure user has logged in and clicked remember decision (otherwise iframe wont work)
Call WebAPI, if 401 response try and get a new token by the below steps...
Have a hidden iframe on the page, which I will set the URL to get a new access-token from the Authorisation Server.
Get the new token from the iframe's hash-fragment, then store this in the SPA and use for all future WebAPI requests.
I guess I would still be in trouble if the FedAuth cookie is stolen.
Any standard or recommended way for the above scenario?
I understand that your problem is that the user will experience an interruption when the access token has expired, by a redirection to the login page of the authorization server. But I don't think you can and should get around this, at least, when using the implicit grant.
As I'm sure you already know, the implicit grant should be used by consumers that can NOT keep their credentials secret. Because of this, the access token that is issued by an authorization server should have a limited ttl. For instance google invalidates their access token in 3600 sec. Of course you can increase the ttl, but it should never become a long lived token.
Also something to note is that in my opinion the user interruption is very minimal, i.e if implemented correctly, the user will only have to authenticate once with the authorization server. After doing that (for example the first time when also authorizing the application access to whatever resources the user controls) a session will be established (either cookie- or token based) and when the access token of the consumer (web app using implicit grant) expires, the user will be notified that the token has expired and re authentication with the authorization server is required. But because a session already has been established, the user will be immediately redirected back to the web app.
If however this is not what you want, you should, in my opinion, consider using the authorization code grant, instead of doing complicated stuff with iframes.
In that case you need a server side web application because then you can keep your credentials secret and use refresh tokens.
Sounds like you need to queue requests in the event that an access token expires. This is more or less how Facebook and Google do it. A simple way using Angular would be to add a HTTP Interceptor and check for HTTP401 responses. If one is returned, you re-authenticate and queue any requests that come in after until the authentication request has completed (i.e. a promise). Once that's done, you can then process the outstanding queue with the newly returned access token from your authentication request using your refresh token.
Happy Coding.
Not sure if I understand your question but,
I would like to write a native javascript single page app which can call a WebAPI directly, however implicit flow does not provide a refresh token.
Summarize facts,
refresh token is sometimes used to be a part of A: Authorization Grant
https://www.rfc-editor.org/rfc/rfc6749#section-1.5
and as you said in implicit flow you dont get back refresh token, but only in Authorization Grant part
https://www.rfc-editor.org/rfc/rfc6749#section-4.2.2
so you can get back refresh token when issuing access token (refresh tokens are always optional)
https://www.rfc-editor.org/rfc/rfc6749#section-5.1
With my SPA (untrusted app), I don't have a refresh-token only an
access token. So instead I:
Ensure user has logged in and clicked remember decision (otherwise
iframe wont work)
Call WebAPI, if 401 response try and get a new
token by the below steps...
Have a hidden iframe on the page, which
I will set the URL to get a new access-token from the Authorisation
Server.
Get the new token from the iframe's hash-fragment, then
store this in the SPA and use for all future WebAPI requests.
SPA(you) have no idea if user selected remember decision. Its in AS direction and should be complete blackbox. Skip this step.
You can try to use access token and wait for result, always.
If access token has expired and you dont have refresh token, you still can create hidden iframe and and try to get new access token.
Lets assume your AS provide option to remember decision and wont change it in future, then: your iframe will get new access token without user interaction, then you will get result back in some unknown time limit.
Result can be checked by setInterval for read specific cookie or iframe postmessage.
If you dont get back data in time limit, then one from following scenarios occured:
lag, AS is slow, connection is slow or time limit is too tight
user didnt select remember decision
In this case:
show iframe with login
I consider scenario above as good practise if AS doesnt provide refresh tokens, but I also guess every AS like that wont provide remember option as well.
StackOverflow <---> Google scenario (I can only guess)
User login, authorization request occured
User logs in, SO gets access token
SO tries to use access token
SO gets back result + refresh token
SO saves refresh token
SO has permanent access to users Google account
In Google o-Auth , the access token will only be valid for 1 hour, so you need to programmatically update your access token in each one hour, simple you can create web api to do so,you need to have a refresh token, and also that refresh token will not be expired , using c# code, I have done this.
if (dateTimeDiff > 55)
{
var request = (HttpWebRequest)WebRequest.Create("https://www.googleapis.com/oauth2/v3/token");
var postData = "refresh_token=your refresh token";
postData += "&client_id=your client id";
postData += "&client_secret=your client secrent";
postData += "&grant_type=refresh_token";
var data = Encoding.ASCII.GetBytes(postData);
request.Method = "POST";
request.ContentType = "application/x-www-form-urlencoded";
request.ContentLength = data.Length;
request.UseDefaultCredentials = true;
using (var stream = request.GetRequestStream())
{
stream.Write(data, 0, data.Length);
}
var response = (HttpWebResponse)request.GetResponse();
string responseString = new StreamReader(response.GetResponseStream()).ReadToEnd();
}
you need to save the last updated date time of the access token somewhere(say in database), so that , whenever you have to make a request , so you can subtract that with current date time , if it is more than 60 minutes , you need to call the webapi to get new token .

Categories

Resources