Skip to content

Commit 2903537

Browse files
authored
Add example for azure b2c OAuth (#1336)
* Add example for azure b2c OAuth This example allows to fetch the tokens for a given user from azure B2C. * Add explanation to B2C example Add specific explanation to the example on how to create you own tenant to test the script for getting token. Fixed one issue on the way, as small difference between costume flow and quick user flows. * Update 03 oauth-authentication.md Change pull request suggestions
1 parent dcce6e5 commit 2903537

File tree

1 file changed

+201
-0
lines changed

1 file changed

+201
-0
lines changed

src/data/markdown/docs/05 Examples/01 Examples/03 oauth-authentication.md

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ The following examples take a set of arguments, shown in the function documentat
1313

1414
<CodeGroup labels={["azure.js"]} lineNumbers={[true]}>
1515

16+
17+
18+
1619
```javascript
1720
import http from 'k6/http';
1821

@@ -56,6 +59,204 @@ export function authenticateUsingAzure(tenantId, clientId, clientSecret, scope,
5659
}
5760
```
5861

62+
### Azure B2C
63+
64+
The following example shows how you can authenticate with Azure B2C using the [Client Credentials Flow](https://docs.microsoft.com/en-us/azure/active-directory-b2c/active-directory-b2c-reference-oauth-code#client-credentials-flow).
65+
66+
This example is based on a JMeter example found at the [azure-ad-b2c/load-tests](https://github.com/azure-ad-b2c/load-tests) repository.
67+
68+
To use this script, you need to:
69+
70+
1. [Set up your own Azure B2C tenant](https://learn.microsoft.com/en-us/azure/active-directory-b2c/tutorial-create-tenant)
71+
* Copy the tenant name, it will be used in your test script.
72+
2. [Register a web application](https://learn.microsoft.com/en-us/azure/active-directory-b2c/tutorial-register-applications?tabs=app-reg-ga)
73+
* Register a single page application with the redirect URL of: https://jwt.ms. That's needed for the flow to receive a token.
74+
* After the creation, you can get the Application (client) ID, and the Directory (tenant) ID. Copy both of them, they'll be used in your test script.
75+
3. [Create a user flow so that you can sign up and create a user](https://docs.microsoft.com/en-us/azure/active-directory-b2c/tutorial-create-user-flows)
76+
* Create a new user, and copy the username and password. They'll be used in the test script.
77+
78+
If you missed copying any of the settings, you can find them in the B2C settings in the Azure portal.
79+
80+
<CodeGroup labels={["azure-b2c.js"]} lineNumbers={[true]}>
81+
82+
```javascript
83+
import http from "k6/http";
84+
import crypto from "k6/crypto";
85+
import { randomString } from "https://jslib.k6.io/k6-utils/1.2.0/index.js";
86+
87+
const B2cGraphSettings = {
88+
B2C: {
89+
client_id: "", // Application ID in Azure
90+
user_flow_name: "",
91+
tenant_id: "", // Directory ID in Azure
92+
tenant_name: "",
93+
scope: "openid",
94+
redirect_url: "https://jwt.ms",
95+
},
96+
};
97+
98+
/**
99+
* Authenticate using OAuth against Azure B2C
100+
* @function
101+
* @param {string} username - Username of the user to authenticate
102+
* @param {string} password
103+
* @return {string} id_token
104+
*/
105+
export function GetB2cIdToken(username, password) {
106+
const state = GetState();
107+
SelfAsserted(state, username, password);
108+
const code = CombinedSigninAndSignup(state);
109+
return GetToken(code, state.codeVerifier);
110+
}
111+
112+
/**
113+
* @typedef {object} b2cStateProperties
114+
* @property {string} csrfToken
115+
* @property {string} stateProperty
116+
* @property {string} codeVerifier
117+
*
118+
*/
119+
120+
/**
121+
* Get the id token from Azure B2C
122+
* @function
123+
* @param {string} code
124+
* @returns {string} id_token
125+
*/
126+
const GetToken = (code, codeVerifier) => {
127+
const url =
128+
`https://${B2cGraphSettings.B2C.tenant_name}.b2clogin.com/${B2cGraphSettings.B2C.tenant_id}` +
129+
`/oauth2/v2.0/token` +
130+
`?p=${B2cGraphSettings.B2C.user_flow_name}` +
131+
`&client_id=${B2cGraphSettings.B2C.client_id}` +
132+
`&grant_type=authorization_code` +
133+
`&scope=${B2cGraphSettings.B2C.scope}` +
134+
`&code=${code}` +
135+
`&redirect_uri=${B2cGraphSettings.B2C.redirect_url}` +
136+
`&code_verifier=${codeVerifier}`;
137+
138+
const response = http.post(url, "", {
139+
tags: {
140+
b2c_login: "GetToken",
141+
},
142+
});
143+
144+
return JSON.parse(response.body).id_token;
145+
};
146+
147+
/**
148+
* Signs in the user using the CombinedSigninAndSignup policy
149+
* extraqct B2C code from response
150+
* @function
151+
* @param {b2cStateProperties} state
152+
* @returns {string} code
153+
*/
154+
const CombinedSigninAndSignup = (state) => {
155+
const url =
156+
`https://${B2cGraphSettings.B2C.tenant_name}.b2clogin.com/${B2cGraphSettings.B2C.tenant_name}.onmicrosoft.com` +
157+
`/${B2cGraphSettings.B2C.user_flow_name}/api/CombinedSigninAndSignup/confirmed` +
158+
`?csrf_token=${state.csrfToken}` +
159+
`&rememberMe=false` +
160+
`&tx=StateProperties=${state.stateProperty}` +
161+
`&p=${B2cGraphSettings.B2C.user_flow_name}`;
162+
163+
const response = http.get(url, "", {
164+
tags: {
165+
b2c_login: "CombinedSigninAndSignup",
166+
},
167+
});
168+
const codeRegex = '.*code=([^"]*)';
169+
return response.url.match(codeRegex)[1];
170+
};
171+
172+
/**
173+
* Signs in the user using the SelfAsserted policy
174+
* @function
175+
* @param {b2cStateProperties} state
176+
* @param {string} username
177+
* @param {string} password
178+
*/
179+
const SelfAsserted = (state, username, password) => {
180+
const url =
181+
`https://${B2cGraphSettings.B2C.tenant_name}.b2clogin.com/${B2cGraphSettings.B2C.tenant_id}` +
182+
`/${B2cGraphSettings.B2C.user_flow_name}/SelfAsserted` +
183+
`?tx=StateProperties=${state.stateProperty}` +
184+
`&p=${B2cGraphSettings.B2C.user_flow_name}` +
185+
`&request_type=RESPONSE` +
186+
`&email=${username}` +
187+
`&password=${password}`;
188+
189+
const params = {
190+
headers: {
191+
"X-CSRF-TOKEN": `${state.csrfToken}`,
192+
},
193+
tags: {
194+
b2c_login: "SelfAsserted",
195+
},
196+
};
197+
http.post(url, "", params);
198+
};
199+
200+
/**
201+
* Calls the B2C login page to get the state property
202+
* @function
203+
* @returns {b2cStateProperties} b2cState
204+
*/
205+
const GetState = () => {
206+
const nonce = randomString(50);
207+
const challenge = crypto.sha256(nonce.toString(), "base64rawurl");
208+
209+
const url =
210+
`https://${B2cGraphSettings.B2C.tenant_name}.b2clogin.com` +
211+
`/${B2cGraphSettings.B2C.tenant_id}/oauth2/v2.0/authorize?` +
212+
`p=${B2cGraphSettings.B2C.user_flow_name}` +
213+
`&client_id=${B2cGraphSettings.B2C.client_id}` +
214+
`&nonce=${nonce}` +
215+
`&redirect_uri=${B2cGraphSettings.B2C.redirect_url}` +
216+
`&scope=${B2cGraphSettings.B2C.scope}` +
217+
`&response_type=code` +
218+
`&prompt=login` +
219+
`&code_challenge_method=S256` +
220+
`&code_challenge=${challenge}` +
221+
`&response_mode=fragment`;
222+
223+
const response = http.get(url, "", {
224+
tags: {
225+
b2c_login: "GetCookyAndState",
226+
},
227+
});
228+
229+
const vuJar = http.cookieJar();
230+
const responseCookies = vuJar.cookiesForURL(response.url);
231+
232+
const b2cState = {};
233+
b2cState.codeVerifier = nonce;
234+
b2cState.csrfToken = responseCookies["x-ms-cpim-csrf"][0];
235+
b2cState.stateProperty = response.body.match('.*StateProperties=([^"]*)')[1];
236+
return b2cState;
237+
};
238+
239+
/**
240+
* Helper function to get the authorization header for a user
241+
* @param {user} user
242+
* @returns {object} httpsOptions
243+
*/
244+
export const GetAuthorizationHeaderForUser = (user) => {
245+
const token = GetB2cIdToken(user.username, user.password);
246+
247+
return {
248+
headers: {
249+
"Content-Type": "application/json",
250+
Authorization: "Bearer " + token,
251+
},
252+
};
253+
};
254+
255+
export default function () {
256+
const token = GetB2cIdToken("[email protected]", "20B2cTest23");
257+
console.log(token);
258+
}
259+
```
59260
</CodeGroup>
60261

61262
### Okta

0 commit comments

Comments
 (0)