We are using custom policies for Sign in and reset password in Azure B2C, when user is resetting his password and after doing all the process, when user tries to login using new password and OTP, below error is getting displayed and then user is continuously asked for new access code.
HTTP/1.1 400 Bad Request
Cache-Control: private
Allow: OPTIONS,TRACE,GET,HEAD,POST
Content-Type: application/json; charset=utf-8
x-ms-gateway-requestid: a6264e6b-e73e-45e1-aab9-71c5916e1215
Access-Control-Expose-Headers: Content-Length, Content-Encoding
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: POST, OPTIONS
Set-Cookie: x-ms-cpim-trans=; domain=****; expires=Fri, 02-Sep-2011 12:14:12 GMT; path=/; SameSite=None; secure; HttpOnly
X-Frame-Options: DENY
Public: OPTIONS,TRACE,GET,HEAD,POST
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Date: Thu, 02 Sep 2021 12:14:12 GMT
Content-Length: 296
{"error":"invalid_grant","error_description":"AADB2C90088: The provided grant has not been issued for this endpoint. Actual Value : B2C_1A_MFA_phone_or_email and Expected Value : B2C_1A_PasswordReset\r\nCorrelation ID: ******\r\nTimestamp: 2021-09-02 12:14:12Z\r\n"}
When the user has changed his password and click continue, below requests generated and you can see first token request is successful, but it is again trying for second token request and getting failed with above error.
Network logs
What we are expecting here is that user should automatically get logged in, once he reset his password, rather then navigating back to log in screen and asking credentials again.
The issue is that you are using the refresh token (RT) from the Password Reset policy to fetch a new token, but you are using the RT against the Sign In policy.
You need to make sure you select the correct Account object in your MSAL cache and use that against the matching B2C policy in your acquireTokenSilent() call.
You can access accounts in your msal instance with instance.getAllAccounts(), it appears to append any new account object each time you make a loginRedirect invocation. You may need to select the correct account in the array when you pass in the account object to acquireTokenSilent
const accounts = instance.getAllAccounts();
const account = accounts[index];
const authResult = await instance.acquireTokenSilent({
scopes: config.authentication.scopes,
account: account
});
### Its my implementation in react ###
const [account, setAccount] = useState();
useEffect(() => {
if (props.authConfig) {
const cachedAccounts = instance.getAllAccounts();
let account;
if (cachedAccounts.length < 1) {
setAccount(undefined);
} else {
account = cachedAccounts.filter((cachedAccount) =>
cachedAccount.username)?.[0];
}
if (account) {
account.name = `${account.idTokenClaims.given_name}
${account.idTokenClaims.family_name}`;
setAccount(account);
}
} else {
setAccount(undefined);
}
}, [props.authConfig]);
We need to make sure we select the correct Account object in
the MSAL
cache and use that against the matching B2C policy in the
acquireTokenSilent() call.
Related
I'm having an issue adding a user to Azure B2C via the Microsoft Graph Api with a custom claim. I have added a claim called "sample", and when I add a user via the registration that claim will be populated with the value that I enter.
However, I need to add users via code not with self-registration. I have the following code that will add a user
// create the connection
var confidentialClientApplication = ConfidentialClientApplicationBuilder
.Create(clientId)
.WithTenantId(tenantId)
.WithClientSecret(clientSecret)
.Build();
var authProvider = new ClientCredentialProvider(confidentialClientApplication);
var client = new GraphServiceClient(authProvider);
var user = new User
{
AccountEnabled = true,
DisplayName = "John Smith",
UserPrincipalName = $"john.smith#{tenantId}",
MailNickname = "john.smith",
PasswordProfile = new PasswordProfile
{
ForceChangePasswordNextSignIn = false,
Password = "P#ssword1"
},
};
var addedUser = await client.Users.Request().AddAsync(user);
This is the same, but using HTTP direct. Which again will add the users
POST /v1.0/users HTTP/1.1
Host: graph.microsoft.com
SdkVersion: postman-graph/v1.0
Content-Type: application/json
Authorization: Bearer [redacted]
User-Agent: PostmanRuntime/7.20.1
Accept: */*
Cache-Control: no-cache
Postman-Token: [redacted]
Host: graph.microsoft.com
Accept-Encoding: gzip, deflate
Content-Length: 325
Connection: keep-alive
cache-control: no-cache
{
"accountEnabled": true,
"displayName": "Joe Blogs",
"mailNickname": "joe.blogs",
"userPrincipalName": "joe.blogs#[redacted].onmicrosoft.com",
"passwordProfile": {
"forceChangePasswordNextSignIn": true,
"password": "P#ssword1"
},
"passwordPolicies": "DisablePasswordExpiration"
}
As I say the above code will add a user, but I can't figure out how to add the custom claim at the same time. The first set of code is using the Microsoft.Graph nuget package. The second is taken from a direct http call in Postman.
I seem to be going round in circles when reading the documentation, can can't seem to see how to do it in the new B2C v2.
Anyone, any idea?
Cheers
Ok worked out how to do this. Need to add the user like above, then update it with the custom claim. They key is that claim has the name in the format of
extension_xxxxx_sample
Where the xxxxx has the application id of the b2c-extensions-app application, which is a in build application.
So once the user has been added then the following code will add the custom claim
var confidentialClientApplication = ConfidentialClientApplicationBuilder
.Create(clientId)
.WithTenantId(tenantId)
.WithClientSecret(clientSecret)
.Build();
var authProvider = new ClientCredentialProvider(confidentialClientApplication);
var client = new GraphServiceClient(authProvider);
var dictionary = new Dictionary<string, object>();
dictionary.Add("extension_xxxxx_sample", "abcd");
await client.Users[$"john.smith#{tenantId}"]
.Request()
.UpdateAsync(new User()
{
AdditionalData = dictionary
});
The key is that the b2c-extensions-app may have the application id of 7e4ff0cd-825a-47ba-a08c-b13a4244b4ce. However, you would add the claim with the following
extension_7e4ff0cd825a47baa08cb13a4244b4ce_sample
not with
extension_7e4ff0cd-825a-47ba-a08c-b13a4244b4ce_sample
I kept getting the following error when using Docusign python API
The specified Integrator Key was not found or is disabled. An Integrator key was not specified
Exception when calling DocuSign API: (401)
Reason: Unauthorized
HTTP response headers: HTTPHeaderDict({'Cache-Control': 'no-cache', 'Content-Length': '165', 'Content-Type': 'application/json; charset=utf-8', 'X-DocuSign-TraceToken': '2818f346-79f7-4c81-a1e1-b8da0f5556a6', 'Date': 'Thu, 17 Jan 2019 17:52:40 GMT', 'Vary': 'Accept-Encoding', 'Strict-Transport-Security': 'max-age=31536000; includeSubDomains'})
HTTP response body: b'{\r\n "errorCode": "PARTNER_AUTHENTICATION_FAILED",\r\n "message": "The specified Integrator Key was not found or is disabled. An Integrator key was not specified."\r\n}'
So I tried creating new Integrator keys, not really knowing what the callback URI should be or much about what I am doing.
I noticed that everytime I created a key, if I refreshed browser, status would go to 'Error' - anybody know how I can generate an Integrator Key that stays in status 'Demo' and that i can use to make API calls
Here is an example of how I am setting up integrator key:
EDIT
I created a new sandbox of my own, when I create an API key in there it doesn't go to Error, stays in status 'Demo'
API key config looks like
and code to call looks like
integrator_key = MY_KEY_FROM_SCREENSHOT
base_url = "https://demo.docusign.net/restapi"
oauth_base_url = 'account-d.docusign.com'
redirect_uri = 'http://localhost:8000'
user_id = MY_EMAIL
#NOTHING HERE REALLY
private_key_filename = os.path.join(BASE_DIR, "keys/docusign_private_key.txt")
oauth_login_url = api_client.get_jwt_uri(integrator_key, redirect_uri, oauth_base_url)
# configure the ApiClient to asynchronously get an access token and store it
#api_client.configure_jwt_authorization_flow(private_key_filename, oauth_base_url, integrator_key, user_id, 3600)
docusign.configuration.api_client = api_client
auth_api = AuthenticationApi()
try:
login_info = auth_api.login(api_password='true', include_account_id_guid='true')
Hmmm I don't know why the Integration Key's status is changing to "Error."
A couple of ideas:
When adding a Redirect URI, include http:// or https:// at the start of the URI. Eg, enter http://127.0.0.1:8000/tools/ds_api
Don't click the Review Transactions option of the Actions drop down. That should only be done when you are ready for the Go-Live test.
I'm new to JWT which stands for Json Web Token. I've confused with couple of its terms: Access Token and Refresh Token.
purpose: I wanna implement a user authorization which logs the user out after two hours of being idle (don't request the site or exit from the browser).
To reach that goal I'm trying to follow the below items:
After the user registers/logs-in in the site, I create Access Token and Refresh Token.
Save the refresh token in the DB or cookie.
After 15 minutes the users token the access token expired.
In case of a user being idle for 2 hours, I remove the refresh token from the cookie or DB, else I renew the access token using refresh token.
Is there any optimized way to reach that purpose?
First of all u need to understand the principle of JWT's and how they are passed between server and client and matched server-side against a secret - here's the doc
The payload can be any arbitrary user data - i.E.: just a usrname or id
Basically you need a service that generates a token on successful authentication (when the user logs in with the proper credentials, i.E.: usr & pwd) and create an additional header with the token to be used in further requests to the server.
// INFO: Function to create headers, add token, to be used in HTTP requests
createAuthenticationHeaders() {
this.loadToken(); // INFO: Get token so it can be attached to headers
// INFO: Headers configuration options
this.options = new RequestOptions({
headers: new Headers({
'Content-Type': 'application/json', // INFO: Format set to JSON
'authorization': this.authToken // INFO: Attach token
})
});
}
// INFO: Function to get token from client local storage
loadToken() {
this.authToken = localStorage.getItem('token');; // Get token and asssign to
variable to be used elsewhere
}
and some functionality to store the user-status i.E.:
// INFO: Function to store user's data in client local storage
storeUserData(token, user) {
localStorage.setItem('token', token); // INFO: Set token in local storage
localStorage.setItem('user', JSON.stringify(user)); // INFO: Set user in local
storage as string
this.authToken = token; // INFO: Assign token to be used elsewhere
this.user = user; // INFO: Set user to be used elsewhere
}
and a logout function to destroy the token in the local storage, i.E.:
// INFO: Function for logging out
logout() {
this.authToken = null; // INFO: Set token to null
this.user = null; // INFO: Set user to null
localStorage.clear(); // INFO: Clear local storage
}
In case you use npm's jsonwebtoken, you can set the ttl of the token when generating it:
const token = jwt.sign({ id: idDB }, "secret", { expiresIn: '24h' });
or whatever ttl you desire, the string "secret" refers to the secret that's matched against the server.
btw: If I understand you correctly, your points number 3 and 4 contradict each other..
After 15 minutes the users token the access token expired.
In case of a user being idle for 2 hours, I remove the refresh token from the cookie or DB, else I renew the access token using refresh token.
in case 4 it will be destroyed anyways in 15 mins if you implemented the logic of number 3 correctly
I have a need to programmatically connect to a customer's SharePoint server that uses OKTA for authentication. I saw this post which looked promising, but cannot seem to get a valid session cookie back from OKTA.
I can successfully call the /api/v1/authn endpoint and get back a sessionToken, but when I turn around and call /api/v1/sessions?additionalFields=cookieToken with that session token, I always received a 403 - Forbidden, with the following json:
{
"errorCode": "E0000005",
"errorSummary": "Invalid Session",
"errorLink": "E0000005",
"errorId": "oaew0udr2ElRfCnZvBFt075SA",
"errorCauses": []
}
Assuming I can get this resolved, I'm not sure of the URL I should call with the cookieToken. Is the url an OKTA endpoint that will redirect to SharePoint or is it an SharePoint endpoint that will setup the session with the cookie?
Update:
I am able to call this okta endpoint -> /api/v1/sessions?additionalFields=cookieToken with my user credentials as json
{
"username": "user#email.com",
"password": "P#ssw0rd"
}
And am able to retrieve a one-time cookie token that can be used with this link to start a SAML session in a browser:
https://[mydomain].okta.com/login/sessionCookieRedirect?redirectUrl=[sharepoint site url]&token=[cookie token]
That works in a browser, the user is automatically authenticated and ends up in SharePoint. However, it seems that this session "setup" is at least partly achieved through javascript as executing the same link in a programmatic HTTP client (such as Apache HTTP Client) does not work. The http client is sent through a couple of redirects and ends up in the SharePoint site, but the user is not authenticated. The response is 403 - Forbidden with the following headers:
403 - FORBIDDEN
Content-Type -> text/plain; charset=utf-8
Server -> Microsoft-IIS/8.5
X-SharePointHealthScore -> 0
SPRequestGuid -> 0ecd7b9d-c346-9081-cac4-43e41f3b159a
request-id -> 0ecd7b9d-c346-9081-cac4-43e41f3b159a
X-Forms_Based_Auth_Required -> https://[sharepoint site]/_login/autosignin.aspx?ReturnUrl=/_layouts/15/error.aspx
X-Forms_Based_Auth_Return_Url -> https://[sharepoint site]/_layouts/15/error.aspx
X-MSDAVEXT_Error -> 917656; Access denied. Before opening files in this location, you must first browse to the web site and select the option to login automatically.
X-Powered-By -> ASP.NET
MicrosoftSharePointTeamServices -> 15.0.0.4709
X-Content-Type-Options -> nosniff
X-MS-InvokeApp -> 1; RequireReadOnly
Date -> Fri, 13 May 2016 15:02:38 GMT
Content-Length -> 13
I'm starting to wonder if this is a lost cause, that OKTA or SharePoint doesn't support programmatic authentication via SAML.
It's possible.
Here is what I did.
1) Get your sessionToken from Okta. You'll need an okta authorization token for that.
2) Do a HttpGet(sharepointEmbeddedLink + "?onetimetoken=" + sessionToken)
Also add this header: new BasicHeader(AUTHORIZATION, String.format("SSWS %s", OKTA_AUTHORIZATION_TOKEN);
3) Next you'll have to parse the html response and get the SAML Arguments: WRESULT, WCTX, WA
4) Next do this - take those 3 and create a string in this format "application/x-www-form-urlencoded". It will be something like this "wa=wsign1.0&wctx=somevalue&wresult=somevalue".
byte[] out = theStringAbove.getBytes;
int length = out.length;
URL url = new URL("https://login.microsoftonline.com/login.srf");
URLConnection con = url.openConnection();
HttpURLConnection http = (HttpURLConnection) con;
http.setRequestMethod("POST"); // PUT is another valid option
http.setDoOutput(true);
http.setInstanceFollowRedirects(true);
http.setFixedLengthStreamingMode(length);
http.setRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
http.setRequestProperty("User-agent", "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.215 Safari/535.1");
http.connect();
http.getOutputStream().write(out);
5) You'll have the saml Token in the response. You'll have to parse an html file again.
6) You'll get the sharepoint siteUrl in step3 or 4 and do this next :)
HttpPost httpPost = new HttpPost(siteUrl + "_forms/default.aspx?wa=wsignin1.0");
byte[] utf8TokenStringBytes = ("t=" + samlToken).getBytes(StandardCharsets.UTF_8);
HttpEntity entity = new ByteArrayEntity(utf8TokenStringBytes);
httpPost.setEntity(entity);
httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
httpPost.setHeader("User-agent", "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.215 Safari/535.1");
HttpResponse response = httpclient.execute(httpPost, httpContext);
If everyting is ok, you'll have some cookie headers that you can use :D
I'm trying to do custom OAuth2 authorization server that will support Resource Owner Password Credentials flow. The authorization server is an WebAPI application hosted in IIS7.5.
I have configured startup class where I register custom OAuthServerProvider (AtcAuthorizationServerProvider).
[assembly: OwinStartup(typeof(ATC.WebApi.AuthorizationServer.Startup))]
namespace ATC.WebApi.AuthorizationServer
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
ConfigureOAuth(app);
HttpConfiguration config = new HttpConfiguration();
WebApiConfig.Register(config);
app.UseWebApi(config);
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
}
public void ConfigureOAuth(IAppBuilder app)
{
OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(30),
Provider = new AtcAuthorizationServerProvider(),
RefreshTokenProvider = new AtcRefreshTokenProvider(),
AuthenticationMode = AuthenticationMode.Passive
};
// Token Generation
app.UseOAuthAuthorizationServer(OAuthServerOptions);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions(){});
}
}
}
In my custom provider class, I override ValidateClientAuthentication() function where I accept both client credentials receiving ways (in Body and in Authorization header).
public class AtcAuthorizationServerProvider : OAuthAuthorizationServerProvider
{
public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
string clientId = string.Empty;
string clientSecret = string.Empty;
// get client credentials from header or from body
if (!context.TryGetBasicCredentials(out clientId, out clientSecret))
{
context.TryGetFormCredentials(out clientId, out clientSecret);
}
//rest of code
Everything works fine when I send client_id and client_secret in body.
POST /ATC.WebApi.AuthorizationServer/token HTTP/1.1
Host: localhost
Accept: application/json
Content-Type: application/x-www-form-urlencoded
Cache-Control: no-cache
grant_type=password&password=123456&username=myUser&client_id=myClient&client_secret=123%40abc
I get access token successfully.
{
"access_token": "3Fk_Ps10i45uL0zeCzIpvEh2WHKE8iJVNtKJ2XGWcQWXsT9jllKf...",
"token_type": "bearer",
"expires_in": 1799,
"refresh_token": "4c1097d17dd14df5ac1c5842e089a88e",
"as:client_id": "myClient"
}
However, if I use DotNetOpenAuth.OAuth2.WebServerClient which passes client_id and client_secret in Authorization header I will recieve 401.1 - Unauthorized HTTP response. I have found out that the ValidateClientAuthentication() is not fired.
Request than looks like this:
POST /ATC.WebApi.AuthorizationServer/token HTTP/1.1
Host: localhost
Accept: application/json
Content-Type: application/x-www-form-urlencoded
Authorization: Basic C16b34lUjEyM0BhYmM=
Cache-Control: no-cache
grant_type=password&password=123456&username=myUser
The question is how to persuade probably the OWIN middle-ware firing my custom Provider in this case?
Well, I finally found out where is the trouble. There was Basic authentication allowed in my IIS, so IIS got the request and tried to Authenticate User which failed and IIS returned 401 Unauthorized immediately. So my OWIN middleware even did not receive the request to processing.