Testing
Effectively testing your DID-Auth integration is crucial for building a reliable and secure application. This guide provides a practical approach to writing automated tests for your authentication flows, using the same patterns found in the did-auth library's own test suite.
The primary strategy is to simulate the entire authentication process programmatically. This involves setting up a test server for your application and using an HTTP client, like axios, to act as the user's wallet.
Testing Architecture Overview#
Your test environment will typically consist of a test runner (like Jest), your application running on a test server, and an HTTP client that mimics the wallet's behavior.
Setting Up the Test Environment#
Before you can test the flow, you need to prepare a few components.
- Test Server: Spin up an instance of your application. The library's test files use a simple
create-test-serverscript, but any method that runs your Express app will work. - In-Memory Storage: Use
MemoryAuthStorageto ensure that sessions are isolated between tests and don't persist state.
const MemoryAuthStorage = require('@arcblock/did-auth-storage-memory');
const tokenStorage = new MemoryAuthStorage();- Mock Wallets: Generate temporary wallets for the application and the simulated user. This allows you to sign and verify messages without needing real secret keys.
const { fromRandom, WalletType } = require('@ocap/wallet');
const Mcrypto = require('@ocap/mcrypto');
// Create a wallet for your application
const appWallet = fromRandom(WalletType({
role: Mcrypto.types.RoleType.ROLE_APPLICATION,
pk: Mcrypto.types.KeyType.ED25519,
hash: Mcrypto.types.HashType.SHA3,
}));
// Create a wallet for the simulated user
const userWallet = fromRandom();Simulating the Authentication Flow#
Here is a step-by-step breakdown of how to simulate a standard login flow in a test.
Step 1: Initiate the Session and Get a Token#
Make a request to your token generation endpoint. The server will create a new session and return a session token and a url for the wallet to connect to.
// 1. Generate a new session token
const { data } = await axios.get(`${server.url}/api/did/login/token`);
const sessionToken = data.token;
const authUrl = decodeURIComponent(new URL(data.url).searchParams.get('url'));Step 2: Simulate the Wallet Scan#
The wallet scans the QR code, which contains the authUrl. Your test simulates this by making a GET request to that URL. The application responds with the initial challenge and the claims it wants the user to provide.
// 2. Simulate wallet scanning the QR code
const { data: challengeResponse } = await axios.get(authUrl);
const authInfo1 = Jwt.decode(challengeResponse.authInfo);Step 3: Respond to the First Challenge (Connect)#
The user approves the connection in their wallet. Your test simulates this by signing the challenge with the user's wallet and POST-ing it back to the url provided in the previous step.
// 3. User approves the connection
const { data: claimsResponse } = await axios.post(
authInfo1.url,
{
userPk: toBase58(userWallet.publicKey),
userInfo: Jwt.sign(userWallet.address, userWallet.secretKey, {
requestedClaims: [],
challenge: authInfo1.challenge
}),
}
);
const authInfo2 = Jwt.decode(claimsResponse.authInfo);Step 4: Provide and Sign the Requested Claims#
If the application requested claims (like a profile), the wallet now prompts the user to approve sharing them. Your test simulates this by creating a JWT with the requested claims, signing it with the new challenge, and POST-ing it.
// 4. User provides the requested profile information
const { data: finalResponse } = await axios.post(
authInfo2.url,
{
userPk: toBase58(userWallet.publicKey),
userInfo: Jwt.sign(userWallet.address, userWallet.secretKey, {
requestedClaims: [{ type: 'profile', email: 'test@example.com', fullName: 'Test User' }],
challenge: authInfo2.challenge,
}),
}
);Step 5: Verify the Final Status#
Finally, poll the status endpoint to confirm that the session was successful.
// 5. Check the session status
const { data: finalStatus } = await axios.get(`${server.url}/api/did/login/status?_t_=${sessionToken}`);
expect(finalStatus.status).toEqual('succeed');Testing Specific Scenarios#
Your tests can be adapted to cover various authentication patterns.
- Multi-Step Workflows: If your
onAuthhandler returns anextWorkflow, your test should capture it from the response and simulate a new wallet scan on the subsequent URL. See Handle Multiple Workflows for more. - User Decline: To test a user declining the request, set
action: 'declineAuth'in theuserInfoJWT payload during the challenge response. The final status should be'error'. - Error Handling: You can test server-side errors by configuring claim handlers to throw exceptions and asserting that the
onErrorhook is called and the session status becomes'error'.