I'm using AWS cognito as a federated identity service to federate Salesforce IDP (we'll have more in the future)
Recently I started getting an "invalid state" error. And after days of research, I found out that:
when the authentication flow starts on cognito there is no error
cognito redirects the user to salesforce
user gets sent back to cognito after login
when the authentication flow starts on salesforce we receive the "invalid_state" error.
user logs in directly over salesforce
salesforce redirects the user to cognito
I need to be able to start the flow from salesforce for the following cases:
the first time user is registered in salesforce
when user reset's password in salesforce
The setup of AWS Cognito via CDK:
const userPool = new UserPool(this, 'duck-user-pool', {
autoVerify: {
email: true,
phone: false,
},
mfa: Mfa.OFF,
mfaSecondFactor: {
sms: false,
otp: false,
},
selfSignUpEnabled: false,
standardAttributes: {
email: {
mutable: true,
required: true,
},
}
});
const salesforceIdP = new CfnUserPoolIdentityProvider(
this,
'salesforceIdP',
{
attributeMapping: {
email: 'email',
family_name: 'family_name',
given_name: 'given_name',
},
providerDetails: {
attributes_request_method: 'GET',
authorize_scopes: 'email openid profile',
oidc_issuer: config.SalesforceBaseUrl,
client_id:
salesforceIdPCredentials.secretValueFromJson(
'CLIENT_ID'
),
client_secret:
salesforceIdPCredentials.secretValueFromJson(
'CLIENT_SECRET'
),
},
providerName: 'salesforce-idp',
providerType: 'OIDC',
userPoolId: userPool.userPoolId,
}
);
const spaClient = userPool.addClient(
'duck-user-pool-client-spa',
{
authFlows: {
userPassword: true,
userSrp: true,
},
generateSecret: false,
refreshTokenValidity: Duration.days(30),
supportedIdentityProviders: [
UserPoolClientIdentityProvider.COGNITO,
UserPoolClientIdentityProvider.custom(
salesforceIdP.providerName
),
],
oAuth: {
callbackUrls: config.CallbackURLs,
flows: {
authorizationCodeGrant: true,
implicitCodeGrant: false,
clientCredentials: false,
},
logoutUrls: config.LogoutURLs,
scopes: [
OAuthScope.EMAIL,
OAuthScope.OPENID,
OAuthScope.PHONE,
OAuthScope.PROFILE,
],
},
}
);
On the salesforce side, configuration was made to redirect the user to following url:
https://sandbox-duck.auth.eu-west-1.amazoncognito.com/oauth2/idpresponse
I've found out cognito is using a query parameter called state (against CSRF attacks) and if the parameter is not in the URL it throws this error.
The setup was working until a month ago but suddenly it stopped working. Is there a way to work around this limitation?
Related
I was hoping that someone might be able to shed some light on issues that I'm having with authentication. I've mostly used this guidance: https://learn.microsoft.com/en-us/azure/active-directory/develop/scenario-spa-overview
I've got a react app that is successfully (I think...) retrieving access tokens for my API:
const account = msalInstance.getActiveAccount();
if (account) {
msalInstance.acquireTokenSilent({
...apiToken,
account: account
}).then((response) => {
setToken(response.accessToken);
});
}
My requests place the token in the authorization header:
Headers
My token looks looks like: Token
API Registration
My API returns 401 whenever I use the Authorize attribute. Because my client is retrieving a token that looks correct, I'm assuming the issue is on my API. This is what I have in my startup auth:
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions
{
AccessTokenFormat = new JwtFormat(
new TokenValidationParameters
{
// Check if the audience is intended to be this application
ValidAudiences = new[] { [MY_API_CLIENT_ID (SAME AS AUDIENCE IN TOKEN)], [MY API REGISTRATION URI] },
// Change below to 'true' if you want this Web API to accept tokens issued to one Azure AD tenant only (single-tenant)
// Note that this is a simplification for the quickstart here. You should validate the issuer. For details,
// see https://github.com/Azure-Samples/active-directory-dotnet-native-aspnetcore
ValidateIssuer = false,
ValidateAudience = false,
ValidateTokenReplay = false,
ValidateIssuerSigningKey = false,
ValidateLifetime = false,
ValidateActor = false, //all false for testing
},
new OpenIdConnectSecurityKeyProvider("https://login.microsoftonline.com/[MY_TENANT_ID]/v2.0/.well-known/openid-configuration")
),
});
Tough silent error here. I noticed that none of the validation callbacks were being invoked. I was missing the package Microsoft.Owin.Host.SystemWeb
i have added federated identities to my React.js application and all worked good initially, however after a few hours the user is not able to log back in. --> "Error: Cannot retrieve a new session. Please authenticate."
I am using a very basic setup :
<Button onClick={() => {
Auth.federatedSignIn({provider: 'Facebook'})
} }> Login with Facebook</Button>
</div>
Auth: {
mandatorySignIn: true,
region: config.cognito.REGION,
userPoolId: config.cognito.USER_POOL_ID,
identityPoolId: config.cognito.IDENTITY_POOL_ID,
userPoolWebClientId: config.cognito.APP_CLIENT_ID,
oauth: {
domain: "domain.auth.us-east-1.amazoncognito.com",
redirectSignIn: 'http://localhost:3000/',
redirectSignOut: 'http://localhost:3000/',
responseType: "token"
}
}```
To me, the issue is related to the expiration of the user token. However, i am not sure what would be the approach to fix this. I read so many posts about that issue but could not find a solution.
I have cloned the repo from the redux-oidc-example and it works for the most part but after a few hours it gives the following error:
Action payload: ErrorResponse: login_required
at new e (oidc-client.min.js:1)
at t [as _processSigninParams] (oidc-client.min.js:1)
at t [as validateSigninResponse] (oidc-client.min.js:1)
at oidc-client.min.js:1
UserManager.js looks like this:
const userManagerConfig = {
client_id: 'js.dev',
client_secret: 'secret',
redirect_uri: `${window.location.protocol}//${window.location.hostname}${window.location.port ? `:${window.location.port}` : ''}/callback`,
response_type: 'id_token token',
scope: 'openid email profile role offline_access',
authority: 'http://localhost:8080',
silent_redirect_uri: `${window.location.protocol}//${window.location.hostname}${window.location.port ? `:${window.location.port}` : ''}/silent_renew.html`,
automaticSilentRenew: true,
filterProtocolClaims: true,
loadUserInfo: true
};
and my identity server config:
{
"Enabled": true,
"ClientId": "js.dev",
"ClientName": "Javascript Client",
"ClientSecrets": [ { "Value": "K7gNU3sdo+OL0wNhqoVWhr3g6s1xYv72ol/pe/Unols=" } ],
"AllowedGrantTypes": [ "implicit", "authorization_code" ],
"AllowedScopes": [ "openid", "email", "profile", "role", "offline_access" ],
"AllowOfflineAccess": true,
"AllowAccessTokensViaBrowser":true,
"RedirectUris": [
"http://localhost:8081/callback",
"http://localhost:8081/silent_renew.html"
],
"PostLogoutRedirectUris": [
"http://localhost:8081"
],
"AccessTokenLifetime": 900,
"RequireConsent": false
}
I noticed that prior to error last valid response had one cookie response(idsrv.session) with empty value with the expiry date set to the previous year:
I believe this to be the root cause of the issue, I searched it on related Github repo and tried to add the Cookie.SameSite to none but it didn't help:
services.AddAuthentication()
.AddSaml(Configuration,externalProviders.UseSaml)
.AddCookie(options => {
options.SlidingExpiration = true;
options.ExpireTimeSpan = TimeSpan.FromDays(30);
options.Cookie.SameSite = SameSiteMode.None;
});
Any idea!
This is likely due to your IDP session expiring - if you call the authorize endpoint with prompt=none but it's unable to satisfy that request because no valid session exists (i.e. authentication cookie does not exist or has expired) then it will return error=login_required.
If this occurs then the correct course of action is to do an interactive (i.e. prompt=login) sign in request in the top level browser window.
After searching the Identity Server 4 repo, I made the following changes to my code:
services.AddIdentityServer(options=>
{
options.Authentication.CookieLifetime = TimeSpan.FromDays(30);
options.Authentication.CookieSlidingExpiration = true;
})
.AddProfileService<ProfileService>()
.AddSigningCertificate(Configuration)
.AddInMemoryClients(Configuration.GetSection("IdentityServer:Clients"))
.AddInMemoryIdentityResources(Resources.GetIdentityResources());
It started working afterward, but you would have to login again after you close the browser or reopen a new tab I guess it's because of the sessionStorage.
When the session expires the signin-callback is being called by STS having a query parameter called 'error' with the value 'login_required'.
In the signin-callback, before completing sign-in, you can check for this query parameter and if it's found you can sign-out also from your web client.
I had the same issue and tried the proposed above, but for me, it actually was SameSiteMode not set correctly on IdentityServer Cookies. It caused Callback error: ErrorResponse: login_required right after login and after N attempts user was logged out.
This helped me https://github.com/IdentityServer/IdentityServer4/blob/main/src/IdentityServer4/host/Extensions/SameSiteHandlingExtensions.cs
What they do is based on this article https://devblogs.microsoft.com/dotnet/upcoming-samesite-cookie-changes-in-asp-net-and-asp-net-core/
Hope this is useful.
Update.
I had another issue related to this when the user was logged out after re-opening a browser (especially on Android Chrome). login_required error was shown. I noticed that session cookie Expires/Max-Age was set to Session and not some future date. Probably because of that check session iframe (with src={identity server url}/connect/checksession) failed as Identity Server thought there was no session as cookie expired.
I tried setting cookie lifetime via options, but it didn't work as expected for some reason. Lifetime was always 14 days:
services.AddIdentityServer(options=>
options.Authentication.CookieLifetime = TimeSpan.FromDays(30);
options.Authentication.CookieSlidingExpiration = true;
})
Then I tried this and it worked for me:
services.ConfigureApplicationCookie(options => {
options.ExpireTimeSpan = sessionCookieLifetime;
options.SlidingExpiration = true;
})
I build an app using aws-amplify that allows login with Cognito users. However, Auth.forgotPassword seems to be sending SMS to mobile device instead of EMAIL. How can I change the behavior such that it sends an email?
Here are related code:
Auth.forgotPassword(this.state.username)
.then(data => this.setState({instruction: 'An email has been sent to your email with a temporary password.', usernameError: false, passwordError: false}))
.catch(err => this.setState({instruction: err.message, usernameError: false, passwordError: false}));
You can configure what you need to verify when forgotPassword API is triggered in the AWS Cognito Console.
Amazon Cognito > Manage User Pool > Choose your pool > General Settings > MFA and verifications > Which attributes do you want to verify?
Also when you trigger the ForgotPassword API you get CodeDeliveryDetails Object in response
const result = await Auth.forgotPassword(username);
console.log(result);
CodeDeliveryDetails: {
"AttributeName": "email",
"DeliveryMedium": "EMAIL",
"Destination": "n***#g***.com"
}
From the result, you get on which medium the code has been delivered and accordingly notify the user in the app.
I'm using office-js-helpers in order to get an OAuth token in my Outlook web add-in so I can use it for OAuthCredentials with the EWS Managed API (code for that is in an Azure App Service using the ASP.NET Web API).
I have configured my app's application registration in my test Office 365 tenant (e.g. mytenant.onmicrosoft.com, which is NOT the same Azure subscription hosting the web app - if that matters) as a Native app with oauth2AllowImplicitFlow set to true. I used a Native app type instead of a Web/API app to bypass an unexpected error indicating my app requires admin consent - even though no application permissions were requested - but that's another story (perhaps I must use Native anyway - not 100% sure).
I made sure that the Redirect URI (aka reply URL) in the app registration points to the same page as the Outlook add-in (e.g. https://mywebapp.azurewebsites.net/MessageRead.html).
Here is my app manifest:
{
"appId": "a11aaa11-1a5c-484a-b1d6-86c298e8f250",
"appRoles": [],
"availableToOtherTenants": true,
"displayName": "My App",
"errorUrl": null,
"groupMembershipClaims": null,
"optionalClaims": null,
"acceptMappedClaims": null,
"homepage": "https://myapp.azurewebsites.net/MessageRead.html",
"identifierUris": [],
"keyCredentials": [],
"knownClientApplications": [],
"logoutUrl": null,
"oauth2AllowImplicitFlow": true,
"oauth2AllowUrlPathMatching": false,
"oauth2Permissions": [],
"oauth2RequiredPostResponse": false,
"objectId": "a11aaa11-99a1-4044-a950-937b484deb8e",
"passwordCredentials": [],
"publicClient": true,
"supportsConvergence": null,
"replyUrls": [
"https://myapp.azurewebsites.net/MessageRead.html"
],
"requiredResourceAccess": [
{
"resourceAppId": "00000003-0000-0000-c000-000000000000",
"resourceAccess": [
{
"id": "e1fe6dd8-ba31-4d61-89e7-88639da4683d",
"type": "Scope"
}
]
},
{
"resourceAppId": "00000002-0000-0000-c000-000000000000",
"resourceAccess": [
{
"id": "311a71cc-e848-46a1-bdf8-97ff7156d8e6",
"type": "Scope"
},
{
"id": "a42657d6-7f20-40e3-b6f0-cee03008a62a",
"type": "Scope"
}
]
},
{
"resourceAppId": "00000002-0000-0ff1-ce00-000000000000",
"resourceAccess": [
{
"id": "2e83d72d-8895-4b66-9eea-abb43449ab8b",
"type": "Scope"
},
{
"id": "ab4f2b77-0b06-4fc1-a9de-02113fc2ab7c",
"type": "Scope"
},
{
"id": "5eb43c10-865a-4259-960a-83946678f8dd",
"type": "Scope"
},
{
"id": "3b5f3d61-589b-4a3c-a359-5dd4b5ee5bd5",
"type": "Scope"
}
]
}
],
"samlMetadataUrl": null
}
I also made sure to add the authority URLs to my add-in's manifest:
<AppDomains>
<AppDomain>https://login.windows.net</AppDomain>
<AppDomain>https://login.microsoftonline.com</AppDomain>
</AppDomains>
This is the code I'm using in the add-in for the authentication with office-js-helpers:
// The Office initialize function must be run each time a new page is loaded.
Office.initialize = function(reason) {
$(document).ready(function () {
// Determine if we are running inside of an authentication dialog
// If so then just terminate the running function
if (OfficeHelpers.Authenticator.isAuthDialog()) {
// Adding code here isn't guaranteed to run as we need to close the dialog
// Currently we have no realistic way of determining when the dialog is completely
// closed.
return;
}
// Create a new instance of Authenticator
var authenticator = new OfficeHelpers.Authenticator();
authenticator.endpoints.registerAzureADAuth('a11aaa11-1a5c-484a-b1d6-86c298e8f250', 'mytenant.onmicrosoft.com');
// Add event handler to the button
$('#login').click(function () {
$('#token', window.parent.document).text('Authenticating...');
authenticator.authenticate('AzureAD', true)
.then(function (token) {
// Consume and store the acess token
$('#token', window.parent.document).text(prettify(token));
authToken = token.access_token;
})
.catch(function (error) {
// Handle the error
$('#token', window.parent.document).text(prettify(error));
});
});
});
};
Now the code in the add-in can properly sign in the user and ask for the required permissions, but after clicking the Accept button on the application authorization step the following error is returned:
AADSTS50011: The reply address 'https://mywebapp.azurewebsites.net' does not match the reply addresses configured for the application: 'a11aaa11-1a5c-484a-b1d6-86c298e8f250'. More details: not specified
The error now returns every time I click the Login button (the user is no longer prompted to sign in). It never did retrieve the token. The full auth URL is:
https://login.windows.net/mydomain.onmicrosoft.com/oauth2/authorize?response_type=token&client_id=a11aaa11-484a-b1d6-86c298e8f250&redirect_uri=https%3A%2F%2Fmywebapp.azurewebsites.net&resource=https%3A%2F%2Fgraph.microsoft.com&state=982599964&nonce=3994725115
What am I doing wrong? Could the issue actually be because the host name of the web app (the redirect URI) does not match the domain of the Azure AD tenant hosting the app registration? If so, how can I grant permissions to Exchange Online from my Azure subscription hosting the web app which does not have Office 365 or Exchange Online? Would I have to add an Azure subscription to my test Office 365 tenant so that it can also host a web application??
From your app manifest, I found that you used https://myapp.azurewebsites.net/MessageRead.html as one of the replyUrls.
And below is the url that you are using to get consent from user.
https://login.windows.net/mydomain.onmicrosoft.com/oauth2/authorize?response_type=token&client_id=a11aaa11-484a-b1d6-86c298e8f250&redirect_uri=https%3A%2F%2Fmywebapp.azurewebsites.net&resource=https%3A%2F%2Fgraph.microsoft.com&state=982599964&nonce=3994725115.
If you observe above url, you mentioned redirect_uri as https://myapp.azurewebsites.net. But redirect_uri should match with at least one of the replyUrls you mentioned in the app manifest.
Try to replace https://myapp.azurewebsites.net with https://myapp.azurewebsites.net/MessageRead.html in authorization url.
I have updated them in below url, if you want you can directly try below url.
https://login.windows.net/mydomain.onmicrosoft.com/oauth2/authorize?response_type=token&client_id=a11aaa11-484a-b1d6-86c298e8f250&redirect_uri=https%3A%2F%2Fmywebapp.azurewebsites.net%2FMessageRead.html&resource=https%3A%2F%2Fgraph.microsoft.com&state=982599964&nonce=3994725115