top of page
Writer's pictureJohn Craddock

Issuing DIDs and VCs with the Microsoft SDK and Authenticator (retired blog)

Updated: Mar 2, 2022

Update 26/11/2021 – Microsoft has depreciated the SDK for verifiable credentials. In their own words:


“From October 31, 2021 certain Microsoft Azure AD Verifiable Credential SDK functionality will stop working in Microsoft Authenticator. Applications and services that currently use the Microsoft Azure AD Verifiable Credential SDK should migrate to the Microsoft Request Service REST API


THIS BLOG HAS NOW BEEN REPLACED - left here for reference


Please go to part 1 of the series to see what's changed. Introduction to the future of identity - DIDs & VCs (xtseminars.co.uk)


In my first blog in the series , I introduced you to Verifiable Credentials (VCs) and Decentralized Identifiers (DIDs). In this blog, I want to show you how you can issue your own VCs using the Microsoft public preview of their Verifiable Credentials (VCs) service. To do this, we will use the Microsoft SDK apps, which are available on GitHub.


The blogs in the series are (No 3 and 5 are coming soon)

  1. Introduction to the future of identity - DIDs & VCs

  2. Issuing DIDs and VCs with the Microsoft SDK app and Authenticator (This one)

  3. Issuing DIDs and VCs with the Microsoft Request Service REST API - replaces no 2

  4. Issuing your own DIDs & VCs with Azure AD and the SDK apps

  5. Issuing your own DIDs & VCs with Azure AD and Microsoft Request Service REST API - replaces no 4

By the end of this blog, you will understand how VCs are issued and verified. Please make sure you are familiar with the first blog before proceeding.


Microsoft has provided a tutorial which you can find here. I am basing this blog on the tutorial but will show you what is really going on.


The Microsoft example app and the Verifiable Claims SDK are written in Node.js. I am pitching this blog at IT Pros and assuming that the reader is a competent IT admin but knows nothing about Node.js or building the development environment. I will not teach you Node.js but will show you how to modify the code samples to better understand what's going on under the hood.


I am assuming you have a reasonable understanding of OpenID Connect and ID Tokens. If you need help in these topics, watch my webinar on "Federated Identity Authentication and Authorization with OpenID Connect and OAuth2.0" or better still come on my identity masterclass https://learn.xtseminars.co.uk/. It would be great to meet you.


Preparing your environment

I recommend you create a Windows 10 VM to host your development environment. This will provide you with a sandbox environment for installing all the tools and development code. The Microsoft documentation for the tutorial is a little sparse when setting up the environment, especially if you are new to GitHub and Node.js. So let me help you. When installing the required apps, choose all the default settings unless otherwise specified below.


Download and install Visual Studio Code from here: https://code.visualstudio.com/Download


Download and install GIT from here: https://gitforwindows.org/


Download and install (see next paragraph) Node.js from here: https://nodejs.org/en/download/


To successfully run the node package manager (npm), you will need Python, and the Visual Studio Build Tools. During the Node.js installation, you can choose the option to install these tools along with Chocolatey. Alternatively, you could install Python, and the Visual Studio Build Tools separately. For the blog, I chose the Chocolatey option.


After installing the tools, sign out and sign in to set the appropriate paths variables.


Node.js runs JavaScript on the computing platform and has excellent performance. It is a hot choice for web development as the same programming language is used inside the browser and on the server.


Git is an open-source version control system. Files can be committed to a repository (storage) and then managed, including rolling back changes to a particular version and cloning. The repository could be local or remote. GitHub is a very popular remote repository where anyone can sign-up and host a public or private repository for free. GitHub is extensively used to make open-source projects publicly available. GitHub makes money through hosting, paid for, full featured private repositories.


Open a command prompt, and you can check the versions of the installed components using:

  • Node -v

    • My version 14.16.1

  • Python -V

    • My version 3.9.4

  • Git –version

    • My version 2.31.1.windows.1

Now we have the tools, we can download the code samples, build the code (automatically installing the necessary modules including the VC SDK) and run the code.


The code will be executed locally on your Windows 10 VM. The authenticator app must be able to connect to the code using HTTPS. The Microsoft sample recommends using Ngrok to act as a reverse proxy providing a public URL through which your code can be accessed.


Download the Ngrok Zip from here: https://ngrok.com/download


Extract it to a suitable location, I placed it here: C:\ngrok


I am going to create a directory structure for running the sample code as follows:

  • C:\VCs\version0

  • C:\VCs\version1

Open a command prompt and switch to the version0 directory, and clone the code from GitHub using:

Examine the cloned code, and you will see an active-directory-verifiable-credentials folder and below that an issuer and a verifier folder. The code samples are run from each of these folders.


We'll leave version0 as a clean copy if we want to refer back to anything once we start making changes. Copy the active-directory-verifiable-credentials folder to the version1 folder.


In the command prompt window, switch to the version1\....\issuer folder and run:

  • npm install

Node Package Manager (NPM) builds the project, integrating external modules, including the Verifiable Credentials SDK. The modules that a project depends on are defined in package.json. When you run npm install, all the packages and their dependencies are added to the node_modules folder.


While the environment is being built, you will see some warning messages which you can ignore.


Now we can run the issuer app. To simplify examining the code and executing the app, let's do this from within VS Code. Open VS Code and using File | open Folder, open the issuer folder: c:\vcs\version1\active-directory-verifiable-credentials\issuer


From the menu bar, use Terminal | New Terminal to open a terminal window. In the terminal window, run the issuer app using:

  • node ./app.js

If prompted by Defender to allow firewall access for Node.js, you do not need to enable it.


You will notice in the VS Code console window it reports: "Example issuer app listening on port 8081!". The example app has created a website and is listening on port 8081. If you want to see it running, you can browse to http://localhost:8081


The website must be available to the Microsoft Authenticator app, and for this, we use Ngrok. Open a new command prompt. Switch to the c:\ngrok directory and execute.

  • ngrok http 8081

You will see something similar to this:

Running Ngrok to act as a reverse proxy allowing the Microsoft Authenticator to connect to our app
Running Ngrok

You can run Ngrok, as an unregistered user, registered user, or paying user. Take a look at https://ngrok.com/pricing for more details.


When using Ngrok with the verifier app, you may receive a message about too many connections.


A message from Ngrok when the connections exceed 20 per minute
Too Many Connections for Ngrok

The verifier app runs JavaScript in the browser with an interval time set to 3000 ms. Exactly 20 connections per minute and right on the cusp of an error. If you register for Ngrok, you get 40 connections/minute. To eliminate the error, you either need to register or modify the interval timer to 4000ms. Without registering Ngrok, the tunnel will periodically time out.


Use your browser (not your mobile) to connect to the HTTPS endpoint, in my example

  • https://76d6e2a666af.ngrok.io

You will see:

Screen Shot of the Microsoft example VC Issuance website showing the Get Credential button
Microsoft Verifiable Credentials Issuance app

Click on Get credentials, and a QR code is shown. The Microsoft Authenticator app is used to scan the QR code.


On your mobile device, if necessary, download and install the Microsoft Authenticator app for Android or iOS


The following steps will tell you all you need to know to add and manage a VC in your user agent (wallet).

Screenshots from the Microsoft Authenticator app going through the issuance of a Verifiable Credential
Using the Microsoft Authenticator to obtain a Verifiable Credential steps 1-3

If this is the first time you have added a VC, you will need to click Add account | Other account… you can then scan the QR code. The QR Code informs the Authenticator where it can retrieve the issuance request. The request defines the VC type that is being requested, lots more detail soon. As an alternative to using a QR Code, if the website is accessed directly from a mobile browser, a deep-link is used to trigger the Authenticator and the issuance request retrieval.

Screenshots of Microsoft Authenticator app as a VC is issued via a B2C ID Token
Using the Microsoft Authenticator to obtain a Verifiable Credential steps 4-6

The first time you add a VC, you will see the Get started option and the Link to the Azure Preview Terms. Once you click Get started, you will see the card for the requested credential and asked to add it.


The credential that is being asked for is specified in an issuance request. The requester has a digital identity and has signed the issuance request using their private key. The requestor's DID is contained in the request, and the associated public key, contained in the DID document, is used to verify the signature. Hopefully, you have read the introduction to this series of blogs and will remember that a DID can be verified as belonging to an organisation via a DNS binding. The Authenticator has checked the signature and the DNS binding and has added the "Verified" icon for did.woodgrovedemo.com.


You may be wondering where the details for the displayed card have come from. The Authenticator has downloaded these details from the Azure AD VC service. The manifest defines how the card should be displayed, the required credentials and where the credentials can be obtained.


Credentials can be :

  • Taken from claims in an ID Token

    • The user signs in to an OpenID Connect identity provider, and an ID Token is issued. Claims in the ID Token are mapped to credentials in the new VC.

  • Taken from other VCs

    • Credentials in other VCs that the user owns are mapped to the new VC.

  • Self-attested

    • Using the Authenticator, the user defines the values for each of the credentials in the new VC. The user (subject) will sign the VC.

  • Static

    • The values for credentials are statically defined.

In the example, you are being asked to sign in to a B2C directory. You have probably guessed, in this instance, the credentials are being taken from an ID Token.


Click the option to sign up now and create an account.

Screenshots showing a VC being added to the Microsoft Authenticator
Using the Microsoft Authenticator to obtain a Verifiable Credential steps 7-9

Once you have created the account, you are automatically signed in and can add the credential. You will now be in a position to present the VC to a verifier. More on the verification process soon.


Notice in step 9 two new icons are showing at the bottom right-hand side. The round icon starts the QR code scanner for VC issuance and verification requests. The Credentials icon switches from the other Authenticator functions and allows you to view your VCs.

Screenshot showing Microsoft Authenticator VC card options
Using the Microsoft Authenticator to obtain a Verifiable Credential steps 10-12

Once you have a VC, you can click on the card for more details. Click on the three vertical dots to deactivate, activate or delete a card. A deactivated card cannot be presented.



Going deep

I promised you some more details, so here we go!


When you run app.js, it is running on Node.js with Express. Express is a framework that simplifies the creation of web applications. Our website is listening on port 8081, and we are making it available via a public endpoint via Ngrok.


Diagram showing how a VC issuance request is generated and then retrieved by the Microsoft Authenticator
Microsoft Authenticator retrieving a Verifiable Credential issuance request

When you click on the Get credential button, JavaScript running in the browser sends a request to the server to build an issuance request.


The request is built based on a credential type and manifest. If you look at the code for issuer/apps.js you will see two constants have been defined:


/////////// Set the expected values for the Verifiable Credential

const credential = 'https://beta.did.msidentity.com/v1.0/3c32ed40-8a10-465b-8ba4-0b1e86882668/verifiableCredential/contracts/VerifiedCredentialExpert';

const credentialType = ['VerifiedCredentialExpert'];


In my next blog, we will be changing these to point to our own VC definitions. We will also look at how the app is signing the issuance request. For now, let's have a look at the manifest. You can download the manifest by entering the credential URL into a browser.

Manifest File downloaded from the Azure AD Verifiable Credentials service

You can see the display characteristic of the card, including the text, logo and background colour. The claims in the display section are the names used in the Authenticator UI. The Input section declares the attestations are to be taken from a B2C directory. The B2C endpoints and other pertinent data are available through the well-known discovery endpoint (.well-known/openid-configuration). The claims that will be required from the ID Token are also declared. These will be mapped to the VC claims (credentials).


After building the issuance request, the app returns a link to the request. JavaScript running in the browser creates a QR Code or a deep-link if a mobile browser (Android, IOS) is being used. The Authenticator scans the QR Code and uses the Link in the code to retrieve the issuance request.


So that you can easily see the issuance request. Use Ctrl+C in the terminal to stop issuer/app.js running. Open issuer/app.js in the editor and after the following line of code:


After:

res.send(session.issueRequest.request);


Add:

console.log(`https://jwt.ms/#id_token=${session.issueRequest.request}`);


I have deliberately not specified the line number as the code may change. If you are typing the code yourself note that the glyph used between the parenthesis is a back-tick (`) and not a single quote mark. Save the file and restart the app.


In Authenticator, deactivate and then delete the VC. Go through the process of reissuing the VC and examine the terminal in VS Code.


The app logs the issuance request to the console prefixing it with https://jwt.ms/#id_token=


Ctrl+click the logged data, and it will open https://jwt.ms and display and decodes the issuance request, which is in the form of a JSON Web Token (JWT).


The JWT consists of three parts, each part separated by '.', header, body and signature. Both the header and body are included for signing.



Issuance request JWT

The header identifies the DID of the signer. You can see the DID under the Key Identifier (kid) attribute in the header.


You will see that the request body includes the issuer's (iss) DID, the requested VC type and manifest URL.


If you look at the kid and iss DID values, you will see that they are the same. The #sig_24bb3074 appended to the kid is not part of the DID. As the DIDs are the same, it means that the issuer of the request signed the request. Issuer/app.js uses the VC SDK to build and sign the request (more in the next blog).


Copy the full DID (include did:ion:) from the iss attribute, and go to https://identity.foundation/ion/explorer/. Paste the DID into the search field. Click search, and it will show you that it is a published DID on ION.

Viewing a published DID on ION

Click Linked Domain, and you will see that the DID is linked to the https://did.woodgrovedemo.com/. This is the domain reported as verified by the Authenticator. If you want to retrieve the document that shows the linked DIDs go to https://did.woodgrovedemo.com/.well-known/did-configuration.json


Once the issuance request has been retrieved, the app has completed its task. It is now down to the Authenticator to complete the issuance cycle.


Going deeper…

My background is in engineering, and I like to really understand what's going on under the hood. This knowledge helps me make really informed decisions in my role as an Identity Architect. I set up Fiddler as a remote proxy and proxied all my phone's traffic through Fiddler. In this way, I could capture all of the interactions between the authenticator app, issuer/app.js, the Azure AD VC service and more…


In this section, I am going to report the results of my Fiddler tracing. I will not go into every detail, but I will give you the big picture and point out the salient interactions. If you want the full details, set up a Fiddler proxy and start investigating.


As previously discussed, the Authenticator retrieves the issuance request.

Issuance request flow

I am not showing the return flows when a request is made for a specific item. For example, Get issuance requests assumes it is returned, hence, the double-headed arrow.


After the request has been retrieved, there is no further interaction with the user's browser or issuer/app.js. I have omitted these from the ongoing diagrams.

Retrieving the DID document and verifying DNS Binding

The issuance request may include the issuer's short-form, or long-form DID. A short-form DID consists of just the ION identifier, for example: did:ion:EiAUeAySrc1qgPucLYI_ytfudT8bFxUETNolzz4PCdy1bw, and it can be used when the DID has been published on ION to retrieve the DID document.


Long-form DIDs are also called private DIDs and are private to a subject. These may not have been published. Consequently, a long-form DID includes the DID document: did:ion:EiAUeAySrc1qgPucLYI_ytfudT8bFxUETNolzz4PCdy1bw:eyJkZWx0YSI6eyJwYXRjaGVzIjpbeyJhY3Rpb24iOiJyZXBsYWNlIiwiZG9jdW1lbnQiOnsicHVibGljS2V5cyI6W3siaWQiOiJzaWdfMjRiYjMwNz……


The Authenticator contacts the DID resolver service and gets back the DID document. It can now verify the signature and check the DNS binding, which verifies the DID belongs to a specific DNS domain. If it cannot validate the DNS binding, you will see a warning that the app or website may be risky.

Risky website/app due to the DNS binding not resolving

The next step is for the Authenticator is to request the manifest details from the Issuer service in Azure AD. You will remember that the manifest specifies how the card should be displayed, the required credentials and the method for obtaining the credentials. In this example, the input method is via an ID Token obtained by the user signing into a B2C directory.

Completing the issuance of a Verifiable Credential

The manifest details are returned as part of a signed JWT. The issuer, identified in the token, is the Azure AD VC service, and the JWT is signed by the service. As before, the signing DID is resolved, and the DNS bindings validated. I have left these the detail off last flow diagram for simplicity.


The IdP configuration document is obtained from the …/.well-known/openid-configuration endpoint on the B2C service. The Authenticator then uses an Authorization Code Flow with PKCE to get an ID Token. During this process, the user will be required to sign in. On my diagram, I have simply shown the flow as "Get IDToken". If you want to understand all details of the flows, including PKCE, please come to my identity masterclass.


The next step is for the Authenticator to send the ID Token to the Azure AD VC service. The service uses the claims in the ID Token to create the required verifiable credentials and return the VCs to the Authenticator.


When the Authenticator sends the ID Token to the VC service, it wraps it in a self-issued ID Token.

Request to the Azure AD Verifiable Credentials service to issue VC

The previous figure shows the pertinent information. The Authenticator generates a new DID for the subject (user). The DID is added to the self-issued token, the audience (aud) identifies the token is for the VC Service, the contract defines the type of VC to be created, and the attestations claim includes the ID Token retrieved from the B2C IdP. The VC service can now generate a VC based on the information in the presented token.


The newly minted VC is returned as a JWT to the Authenticator:

Verifiable Credential issued by the Azure AD Verifiable Credentials service

Once again, I have simplified the diagram and just included the pertinent details. You will see that the Authenticator now holds a signed VC, including the subject DID and the appropriate credentials. Taking the example from my first blog, it's the digital equivalent of our real-world driving licence.


I will cover the three URL endpoints in the issued VC when we encounter them being used.

Issuer and Holder, the Verifier is missing

We now have two members of our trio, Issuer and Holder, but how does a verifier operate? Read on.


Running the Verifier App

Open VS Code and using File | open Folder, open the verifier folder:

C:\vcs\version1\active-directory-verifiable-credentials\verifier


In the terminal window, check you are in the version1\....\verifier folder and run:

  • npm install

While the environment is being built, you will see some warning messages which you can ignore.


In the terminal window, run the verifier app using:

  • node ./app.js

You will notice in the VS Code console window it reports: "Example app listening on port 8082!".


In a separate command window, switch to the c:\ngrok directory and execute.

  • ngrok http 8082


Use your browser (not your mobile) to connect to the HTTPS endpoint.

You will see:

Microsoft Verifiable Credentials verification app

If you get the error message about too many connections at any time, you will either need to register your copy of Ngrok or change the JavaScript interval timer to 4000ms. To change the timer, open …\verifier\public\index.html search for 3000 and replace with 4000. There is only one instance. After you have made the change, you will need to clear the browser cache and refresh (Ctrl+F5).


Click on Verify credentials, and a QR code is shown. The Microsoft Authenticator app is used to scan the QR code. This app has similar behaviour to the issuing app. When you click the button, a presentation request is generated and signed by the verifier application, a link is passed back to the browser and displayed as a QRCode.


The presentation request defines the VC/credentials that are required by the verifier.


You can see the requested VC in the Authenticator. Once you have presented the VC (Allow), you can click on the card and examine the card activity.

Microsoft Authenticator showing presentation request and usage

…\verifier\app.js builds the details for the presentation request using the two constants, credential and credentialType. The issuerDid constant is used by the app to verify the issuer. In my next blog we will be changing these values.


/////////// Set the expected values for the Verifiable Credential

const credential = 'https://beta.did.msidentity.com/v1.0/3c32ed40-8a10-465b-8ba4-0b1e86882668/verifiableCredential/contracts/VerifiedCredentialExpert';

const credentialType = 'VerifiedCredentialExpert';

const issuerDid = ['did:ion:EiAUeAySrc1qgPucLYI_ytfudT8bFxUETNolzz4PCdy1bw:eyJkZWx0YSI6eyJwYXRjaGVzIjpbeyJhY3Rpb24iOiJyZXBsYWNlIiwiZG9jdW1lbnQiOnsicHVibGljS2V5cyI6W3siaWQiOiJzaWdfMjRiYjMwNzQiLCJwdWJsaWNLZXlKd2siOnsiY3J2Ijoic2VjcDI1NmsxIiwia3R5IjoiRUMiLCJ4IjoiRDlqYUgwUTFPZW1XYVVfeGtmRzBJOVoyYnctOFdLUFF2TWt2LWtkdjNxUSIsInkiOiJPclVUSzBKSWN0UnFQTHRCQlQxSW5iMTdZS29sSFJvX1kyS0Zfb3YyMEV3In0sInB1cnBvc2VzIjpbImF1dGhlbnRpY2F0aW9uIiwiYXNzZXJ0aW9uTWV0aG9kIl0sInR5cGUiOiJFY2RzYVNlY3AyNTZrMVZlcmlmaWNhdGlvbktleTIwMTkifV0sInNlcnZpY2VzIjpbeyJpZCI6ImxpbmtlZGRvbWFpbnMiLCJzZXJ2aWNlRW5kcG9pbnQiOnsib3JpZ2lucyI6WyJodHRwczovL2RpZC53b29kZ3JvdmVkZW1vLmNvbS8iXX0sInR5cGUiOiJMaW5rZWREb21haW5zIn1dfX1dLCJ1cGRhdGVDb21taXRtZW50IjoiRWlBeWF1TVgzRWtBcUg2RVFUUEw4SmQ4alVvYjZXdlZrNUpSamdodEVYWHhDQSJ9LCJzdWZmaXhEYXRhIjp7ImRlbHRhSGFzaCI6IkVpQ1NvajVqSlNOUjBKU0tNZEJ1Y2RuMlh5U2ZaYndWVlNIWUNrREllTHV5NnciLCJyZWNvdmVyeUNvbW1pdG1lbnQiOiJFaUR4Ym1ELTQ5cEFwMDBPakd6VXdoNnY5ZjB5cnRiaU5TbXA3dldwbTREVHpBIn19'];


To see the details of the presentation request, edit …verifier/app and after the following line of code:

After:

res.send(session.presentationRequest.request);


Add:

console.log("Presentation request:");

console.log(`https://jwt.ms/#id_token=${session.presentationRequest.request}`);


To see the presentation returned to the app, after the following line of code:

After:

const token = req.body.id_token;


Add:

console.log("Presentation response:");

console.log(`https://jwt.ms/#id_token=${token}`);


I have encountered a problem with VS Code. If the URL is too long, Ctrl+Click does not work. To display the presentation response, you will need to copy the complete URL and paste it into your browser's address bar.


Save the file and restart the app.


If you examine the presentation request, you will see that it asks for the presentation of a VC with a URI of VerifiableCredentialExpert and the manifest URL is declared. The manifest URL allows the Authenticator to try and get the requested VC if it doesn't already hold it.


Examine the Presentation response, and you will see it is a self-issued ID Token, but it gets pretty complicated. The ID Token contains a JWT, which has the presentation in it. The Presentation JWT contains JWTs for any VCs that are being presented. A single VC is requested in our example, but it is possible to ask for multiple VCs to be included.

Verifiable Credentials presentation

The following diagram shows more detail. Once again, I am only showing the pertinent attributes.

Linking between the components of a presentation

Going deep again

To complete this part of the story, I will show the key components of the flow as captured using Fiddler.

Microsoft Authenticator obtaining the Presentation request

The first part of the flow is almost identical to the issuance flow except for building a presentation request and the browser periodically polling …\verifier\app.js for the confirmation that the presentation has been verified. The next part of the flow validates the signature of the verifier and the DNS binding.


Presentation request flow including DID exchange

From my first blog, you may remember, "I can hear you thinking…. The VC was issued to a particular digital ID. How can a user present the VC when their digital ID has changed?" Earlier in this blog, I mentioned three endpoints, and I would cover them as we encountered them. Here's the first one, the exchangeService endpoint.


The Authenticator generates a new DID for the subject and then sends a request to the endpoint to swap the current DID in the VC for the new one. Of course, the appropriate signatures are used to secure the exchange. The Authenticator can now generate the presentation and submit it. \Verifier\app.js validates the presentation, and the browser retrieves the confirmation.


Nearly the end…

If you have got this far, well done, and probably you realise why I have split this blog on issuing and verifying into two parts. Part 2 will get you creating your own VCs using the Azure AD Verifiable Credentials Service.


When technology is brand new, and there are endless possibilities for its utilisation, I think we become fully empowered to push boundaries and become genuinely creative if we understand how things work. I hope this blog has helped your understanding of DIDs & VCs.


Thank you for reading this blog, and stay tuned for the next one. Please let your friends and colleagues know about the blog via LinkedIn and Twitter, don't forget to include me, so I see it! twitter: @john_craddock and/or www.linkedin.com/in/johnxts


My next identity masterclasses for CET and EST are in March 2022. Why don't you join me for an action-packed week?

  • Monday 7th - Friday 11th March 2022 9:00 - 17:00 CET

  • Monday 14th - Friday 18th March 2022 8:00 - 16:00 EST

2,476 views1 comment

Recent Posts

See All

1件のコメント


Nora J. Ellis
Nora J. Ellis
11月11日

Offering DIDs and VCs is a revolution in the field of identity. These technologies also enable security and trust in Internet transactions while ensuring that data are accurate and credible. While organizations integrate this innovation, students in search of efficient and cheap services a affordable dissertation writing service may also elevated security in academic.

いいね!
bottom of page