Here using keycloak to get idToken
Getting error when using signInWithCredential in firebase
Error
The nonce in ID Token "68963ae6-e032-42b4-a7b1-5672f053acf5" does not match the SHA256 hash of the raw nonce "68963ae6-e032-42b4-a7b1-5672f053acf5" in the request.
const provider = new OAuthProvider('oidc.inspect-app');
const auth = getAuth();
const credential = provider.credential({idToken:orginApp.idToken, rawNonce:
orginApp.tokenParsed.nonce});
signInWithCredential(auth, credential)
.then((result:any) => {
//code logic
})
})
I had to switch to code flow to make it work.
In this example i used ionic and OAuth2Client (Capacitor Plugin) to retrieve credentials after native signIn for ios and android.
public async signIn() {
// Ionic Capacitor Plugin
const result = await OAuth2Client.authenticate(OAUTH_OPTIONS);
const provider = new OAuthProvider('oidc.your-keycloak-provider');
provider.addScope('email');
provider.addScope('profile');
provider.addScope('openid');
const credential = provider.credential({
accessToken: result.access_token_response['access_token'],
idToken: result.access_token_response['id_token']
});
return signInWithCredential(this.auth, credential);
}
Related
I have setup a project with core identity and successfully register users via my endpoint but I am unable to get the claims principal to my client browser.
using postman to make a post:
https://localhost:7273/signin?username=test&password=Test123!
I can then do the following get in postman
https://localhost:7273/getuserinfo
To successfully get my userclaims from the cookie and retrieve the userinfo.
However when I do the same requests in my nuxt application with axios no cookie is ever set and my response.headers is undefined.
const res = await this.$axios.$post("https://localhost:7273/signin", {}, { params : {username : "test", password: "Test123!"}},
{withCredentials: true}).then(function(response) {
console.log({ headers: response.headers
});
});
In my backend I get signinResult = succeeded
[Route("Signin")]
[HttpPost]
public async Task<IActionResult> Signin(string username, string password)
{
var signinResult = await _signinManager.PasswordSignInAsync(username, password, true, false);
var principal = HttpContext.User as ClaimsPrincipal;
return Ok();
}
What link am I missing that is making this work in postman but not in my webapp?
I am new to Flutter and AWS and I made a small project with facebook authentication.
using:
amazon_cognito_identity_dart: ^0.0.22
flutter_facebook_login: ^3.0.0
On AWS I:
Configured an API with a POST method
Created a new Cognito User Pool
Created a new App Client in the new Cognito User Pool
Created a new Cognito Identity Pool with Facebook AppID from Facebook
I have a sign in function
signInFacebook() async {
final facebookLoginResult = await signInWithFacebook();
final credentials = new Credentials(
cognitoIdentityPoolId,
cognitoUserPoolId,
cognitoClientId,
facebookLoginResult.accessToken.token
);
a method signInWithFacebook:
import 'package:flutter_facebook_login/flutter_facebook_login.dart';
Future<FacebookLoginResult> signInWithFacebook() async {
final facebookLogin = FacebookLogin();
final facebookLoginResult = await facebookLogin.logIn(['email']);
return facebookLoginResult;
}
a class Credentials:
import 'package:amazon_cognito_identity_dart/cognito.dart';
class Credentials {
final CognitoCredentials _cognitoCredentials;
final String _token;
Credentials(String identityPoolId, String userPoolId, String clientId, this._token)
: _cognitoCredentials = new CognitoCredentials(identityPoolId, new CognitoUserPool(userPoolId, clientId));
Future<CognitoCredentials> get cognitoCredentials async {
await _cognitoCredentials.getAwsCredentials(_token);
return _cognitoCredentials;
}
}
and a credentials.dart with the Cognito credentials and the endpoint of the API
The problem is when it executes the method:
await _cognitoCredentials.getAwsCredentials(_token);
I get the following error:
CognitoClientException{statusCode: 400, code: NotAuthorizedException, name: NotAuthorizedException, message: Invalid login token. Not a valid OpenId Connect identity token.}
Why is the token I receive from facebook not valid?
Am I missing something?
i solved updating to the
amazon_cognito_identity_dart_2: ^0.1.19
and using the method
await _cognitoCredentials.getAwsCredentials(_token, 'graph.facebook.com');
I'm trying to implement social login using Microsoft account in AWS Cognito User Pools.
I followed documentation and the solution mentioned in this thread:
https://forums.aws.amazon.com/thread.jspa?threadID=287376&tstart=0
My problem is with setting the issuer to allow multiple tenants.
This issuer works only for private accounts:
https://login.microsoftonline.com/9188040d-6c67-4c5b-b112-36a304b66dad/v2.0
This issuer works only for accounts in our directory (tenant):
https://login.microsoftonline.com/AZURE_ACTIVE_DIRECTORY/v2.0
This issuer does not work at all. I get bad issuer error or bad request after sign in with Microsoft:
https://login.microsoftonline.com/common/v2.0
I need to have one oidc provider that will work for any Microsoft account (all tenants) is that even possible?
If I set issuer tenant to common in the AWS Cognito oidc config, then this starts the correct Microsoft flow, but I assume the check for issuer in Cognito fails because Microsoft always returns the specific tenant id inside the jwt token as part of the issuer.
Additional info from microsoft documentation I have checked:
https://learn.microsoft.com/de-de/azure/active-directory/develop/v2-protocols-oidc
https://learn.microsoft.com/de-de/azure/active-directory/develop/id-tokens
I am a colleague of Dragan and after a lot of trying we have found a solution in our team that actually works. Just to notice that we actually had access to premium AWS and Microsoft support, but they couldn't help us. The AWS Cognito Team is aware of the issue, but seems like it has no priority - since nearly a year there hasn't been any fix.
Explanation of the flow
We authenticate against microsoft using their javascript library msal in the frontend (no Cognito involved). We receive a JWT token and use this one to create a normal Cognito user in the user pool. The e-mail is read from the microsoft token and the password is autogenerated with a secure random (as long as possible). Additionally we send the microsoft token as custom user attribute. In PreSignUp Lambda we auto activate the user if the microsoft token is valid, so no password verify e-mail is sent to the user. Back in the frontend we use the amplify custom auth challenge signIn with the e-mail we have cached in the frontend. Now we go through DefineAuthChallenge and then CreateAuthChallenge. CreateAuthChallenge doesn't do anything as the microsoft token is our challenge and doesn't need to be created. Back in the frontend we call CustomChallenge containing sessionKey and microsoft token. We are now in VerifyChallenge Lambda where we verify the microsoft token itself using open source JWT libraries. The flow goes back through DefineAuthChallenge where we only allow one try. Finally the user receives the Cognito tokens from Cognito.
The following snippets are the full code snippets for the Lambdas. I had to remove some specific stuff from our project so hopefully didn't break anything while doing so. All files are the index.js and no additional files are needed for the Lambdas. You could for sure outsource some duplicated code, which we haven't done yet. The FE code is not included here.
PreSignUp Lambda
const jwksClient = require('jwks-rsa');
const jwt = require('jsonwebtoken');
const client = jwksClient({
jwksUri: 'https://login.microsoftonline.com/common/discovery/v2.0/keys'
});
const options = {
algorithms: ['RS256']
};
function getKey(header, callback) {
client.getSigningKey(header.kid, function (err, key) {
const signingKey = key.publicKey || key.rsaPublicKey;
callback(null, signingKey);
});
}
const verifyMicrosoftToken = async (jwt, token, key) => {
if (!token) return {};
return new Promise((resolve, reject) =>
jwt.verify(token, key, options, (err, decoded) => err ? reject({}) :
resolve(decoded))
);
};
exports.handler = async (event) => {
const email = event.request.userAttributes.email.toLowerCase();
//verify microsoft and auto enable user
if (event.request.userAttributes['custom:msalIdtoken']) {
const token = await verifyMicrosoftToken(
jwt, event.request.userAttributes['custom:msalIdtoken'], getKey
);
const emailFromToken = token.email !== undefined ? token.email : token.preferred_username;
if (token && emailFromToken.toLowerCase() === email) {
event.response.autoConfirmUser = true;
event.response.autoVerifyEmail = true;
}
}
return event;
};
DefineAuthChallenge Lambda
exports.handler = (event, context, callback) => {
if (event.request.session &&
event.request.session.length > 0 &&
event.request.session.slice(-1)[0].challengeName === 'CUSTOM_CHALLENGE' &&
event.request.session.slice(-1)[0].challengeResult === true){
console.log("Session: ", event.request.session);
event.response.issueTokens = true;
event.response.failAuthentication = false;
} else {
event.response.failAuthentication = false;
event.response.issueTokens = false;
event.response.challengeName = 'CUSTOM_CHALLENGE';
}
// Return to Amazon Cognito
callback(null, event);
};
CreateChallenge Lambda
exports.handler = (event, context, callback) => {
if (event.request.challengeName === 'CUSTOM_CHALLENGE') {
event.response.publicChallengeParameters = {};
event.response.publicChallengeParameters.dummy = 'dummy';
event.response.privateChallengeParameters = {};
event.response.privateChallengeParameters.dummy = 'dummy';
event.response.challengeMetadata = 'MICROSOFT_JWT_CHALLENGE';
}
callback(null, event);
};
VerifyAuthChallenge Lambda
const AWS = require('aws-sdk');
const jwksClient = require('jwks-rsa');
const jwt = require('jsonwebtoken');
const client = jwksClient({
jwksUri: 'https://login.microsoftonline.com/common/discovery/v2.0/keys'
});
const options = {
algorithms: ['RS256']
};
function getKey(header, callback){
client.getSigningKey(header.kid, function(err, key) {
const signingKey = key.publicKey || key.rsaPublicKey;
callback(null, signingKey);
});
}
exports.handler = (event, context, callback) => {
if(event.request.challengeAnswer){
jwt.verify(event.request.challengeAnswer, getKey, options, function(err, decoded) {
if(decoded){
const email = decoded.email !== undefined ? decoded.email : decoded.preferred_username;
if (email.toLowerCase() === event.request.userAttributes['email'].toLowerCase()) {
event.response.answerCorrect = true;
// it is necessary to add this group to user so in BE we can resolve microsoft provider
const cognitoIdentityServiceProvider = new AWS.CognitoIdentityServiceProvider();
var params = {
GroupName: "CUSTOM_MICROSOFT_AUTH",
UserPoolId: event.userPoolId,
Username: event.userName
};
cognitoIdentityServiceProvider.adminAddUserToGroup(params, function (err) {
if (err) {
console.log("Group cannot be added to the user: " + event.userName, err);
}
callback(null, event);
});
}
}
if(err){
console.log(err);
}
});
}else{
event.response.answerCorrect = false;
callback(null, event);
}
};
Frontend (Angular component)
ngOnInit() {
// after microsoft successful sign in we need to continue to cognito authentication
this.authMsalService.handleRedirectCallback((authError, response) => {
if (authError) {
this.showLoginError = true;
return;
}
this.signUpOrSignInWithMicrosoftToken(response.idToken.rawIdToken);
});
}
onSignInWithProvider(provider: string) {
this.cognitoService.clearAuthData();
if (provider === SINGLE_SIGN_ON_PROVIDER.MICROSOFT) {
this.authMsalService.loginRedirect({
scopes: ['user.read', 'email'],
});
} else {
const options: FederatedSignInOptions = {provider: CognitoHostedUIIdentityProvider[GeneralUtils.capitalize(provider)]};
this.socialSignIn(options);
}
}
private socialSignIn(options: any): void {
Auth.federatedSignIn(options).catch(() => {
this.showLoginError = true;
this.uiBlockerService.setIsUiBlocked(false);
});
}
private signUpOrSignInWithMicrosoftToken(microsoftIdToken: string) {
this.uiBlockerService.setIsUiBlocked(true);
const attributes = {};
const userName: string = this.authMsalService.getAccount().userName.toLowerCase();
attributes['email'] = userName;
attributes['custom:msalIdtoken'] = microsoftIdToken;
if (this.authMsalService.getAccount().idToken['family_name']) {
attributes['family_name'] = this.authMsalService.getAccount().idToken['family_name'];
}
if (this.authMsalService.getAccount().idToken['given_name']) {
attributes['given_name'] = this.authMsalService.getAccount().idToken['given_name'];
}
Auth.signUp({
username: userName,
password: SSOUtils.getSecureRandomString(20),
attributes: attributes
}).then(user => {
// register
// after successfully signup we need to continue with authentication so user is signed in automatically
this.authenticateWithMicrosoftToken(microsoftIdToken);
}).catch(error => {
// login
// if user is already registered we continue with sign in
if (error.code === 'UsernameExistsException') {
this.authenticateWithMicrosoftToken(microsoftIdToken);
}
this.uiBlockerService.setIsUiBlocked(false);
});
}
private authenticateWithMicrosoftToken(microsoftIdToken: string) {
const userName: string = this.authMsalService.getAccount().userName.toLowerCase();
Auth.signIn(userName).then(cognitoUser => {
// after sign in is started we need to continue with authentication and we sent microsft token
Auth.sendCustomChallengeAnswer(cognitoUser, microsoftIdToken);
});
}
Here are some links we used
https://aws.amazon.com/blogs/mobile/implementing-passwordless-email-authentication-with-amazon-cognito/
https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-challenge.html
https://learn.microsoft.com/en-us/azure/active-directory/develop/msal-overview
PostScript
If you find any security relevant issue in this code, please contact me privately and our company will show some appreciation ($) depending on severity.
Root cause of the issue:
When we integrate Microsoft login via OIDC, we have a couple of options based on our requirement.
In the case where only the users with work or school accounts from Azure AD can sign in to the application then, we have to refer to https://login.microsoftonline.com/organizations/v2.0/.well-known/openid-configuration
Also in the case where any user who has a Microsoft account (work or school Azure AD accounts, OR personal - outlook, live, etc) can sign in to the application then, we have to refer to
https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration
In those metadata files, we can see that the issuer is https://login.microsoftonline.com/{tenantid}/v2.0.
So basically, depending on the end user’s Azure AD tenant, the id_token issued by Azure AD will have a different value for issuer (iss) claim.
Which means the iss claim is dynamically change for each user.
Right now, that dynamic behavior isn’t being supported by Cognito.
In Cognito, under the OIDC Identity provider configurations, we have to specify the issuer manually and we can only specify one.
So Cognito can not properly validate the id_token issued by Azure AD. and it returns an error saying Bad id_token issuer.
Another Workaround:
There are identity providers which support this dynamic iss claim behavior of Azure AD. (Auth0, Azure AD B2C, etc). So we can select one of them and configure that to communicate with Microsoft (Azure AD) via OIDC. Then add that IDP as an OIDC identity provider in Cognito. Basically we place that IDP in between Cognito and Microsoft (Azure AD).
I avoided this (tenancy/issuer) problem by avoiding usage of the userpool, and directly interacting with the azure endpoints https://login.microsoftonline.com/common/oauth2/v2.0/authorize etc..
I still have to use the identitypool, to map to IAM role.
Understandably, this is more work than having the userpool handle token stuff, but this is the only way I found it to work with all azure ad accounts.
I am using Congnito User Pool to perform API Gateway authorization which is working fine. Now, I am trying to add Instagram as one of the login provider, for that I created Custom Authentication Provider in Federated Identities and using code below:
var cognitoidentity = new AWS.CognitoIdentity({ apiVersion: '2014-06-30' });
var params = {
IdentityPoolId: 'us-east-1:7d99e750-.....',
Logins: {
'login.instagram': 'Access-Token-Returned-By-Instagram',
},
TokenDuration: 60
};
cognitoidentity.getOpenIdTokenForDeveloperIdentity(params, function (err, data) {
if (err) {
console.log(err, err.stack);
} else {
console.log(data);
var idParams = {
IdentityId: data['IdentityId'],
Logins: {
'cognito-identity.amazonaws.com': data['Token']
}
};
cognitoidentity.getCredentialsForIdentity(idParams, function (err2, data2) {
if (err2) console.log(err2, err2.stack); // an error occurred
else console.log(data2); // successful response
});
}
});
I am able to get accessToken and sessionToken, however, I am still unable to find a way to get idToken and accessToken which is required by API Gateway to authorize the incoming request.
I tried looking into SDK as well as AWS forum, but I am still unable to find a way to use custom federated identity provider to authorize API Gateway which use a cognito user pool.
I am going to take a whirl at this....
Do you have a cognito user?
No
import { CognitoUser, CognitoUserPool, AuthenticationDetails } from "amazon-cognito-identity-js";
let cognitoUser;
const userPool = new CognitoUserPool({
UserPoolId: config.USER_POOL.pool_Id, //your userpool id
ClientId: config.appClientId, //your appClient
});
const userData = {
Username: 'user name',
Pool: userPool
};
cognitoUser = new CognitoUser(userData);
/* Should now have a cognitoUser */
Authenticate your cognito user
const authenticationData = {
Username : payload.userName,
Password : payload.password,
};
const authenticationDetails = new AuthenticationDetails(authenticationData);
cognitoUser.authenticateUser(authenticationDetails, {
onSuccess: function (result) {
const accessToken = result.getAccessToken().getJwtToken();
/* Use the idToken for Logins Map when Federating User Pools with identity pools or when passing through an Authorization Header to an API Gateway Authorizer*/
const idToken = result.idToken.jwtToken;
/*
Do something with
idToken
accessToken
I would write them to a file or encrypt them and store them on the localStorage OR encrypt and save in REDUX store.
*/
},
onFailure: function(err) {
//handle error
},
});
I am using aws amplify and I know that the tokens get automatically refreshed when needed and that that is done behind the scenes.
What I need to do is change a custom attribute on the user in the cognito user pool via a Lambda backend process. This I can do, and it is working. However, the web client user never sees this new custom attribute and I am thinking the only way they can see it is if the token gets refreshed since the value is stored within the JWT token.
The correct solution as of 2021 is to call:
await Auth.currentAuthenticatedUser({bypassCache: true})
Here is how you can update tokens on demand (forcefully)
import { Auth } from 'aws-amplify';
try {
const cognitoUser = await Auth.currentAuthenticatedUser();
const currentSession = await Auth.currentSession();
cognitoUser.refreshSession(currentSession.refreshToken, (err, session) => {
console.log('session', err, session);
const { idToken, refreshToken, accessToken } = session;
// do whatever you want to do now :)
});
} catch (e) {
console.log('Unable to refresh Token', e);
}
Like it's said here:
https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-tokens-with-identity-providers.html
The access token and ID token are good for 1 hour. With Amplify you can get the info about the session using currentSession or currentUserInfo in Auth class to be able to retrieve information about tokens.
Undocumented, but you can use the refreshSession method on the User. Your next call to currentAuthenticatedUser and currentSession will have updated profile attributes (and groups)
User = Auth.currentAuthenticatedUser()
Session = Auth.currentSession()
User.refreshSession(Session.refreshToken)
#andreialecu wrote the correct answer. For full code to get the JWT:
static async amplifyRefresh() {
try {
const currentUser = await Auth.currentAuthenticatedUser({ bypassCache: true })
const currentSession = await Auth.currentSession()
const jwt = currentSession.getIdToken().getJwtToken()
// do what you want
} catch (error) {
console.log("error refreshing token: ", error)
throw error
}
}