Preview
Microsoft Microsoft
Microsoft home
  • Documentation
  • Code Samples
Show / Hide Table of Contents

DID sign-in for web applications

Updated: June 2, 2020


You can allow users to sign-in to your app with their DID instead of a username & password or a social account like Google or Facebook. In this article, you'll build a simple web app that authenticates a user with their DID using experimental DID authentication libraries from the Decentralized Identity Foundation (DIF).

Prerequisites

Before getting started, you should:

  • Install the test user agent as a chrome extension.
  • Register a DID for your user in the user agent.
  • Install NodeJS 8 or later, which will be used to run our web app.
  • Install OpenSSL or use bash to generate key pairs for the web server.

Download code sample

To download the code, click here to download a zip file and unzip it to a location of your choice.

Set up the web server

First, run npm install in the directory where you unzipped the sample code. This will install all required dependencies for our web app.

npm install

Next, generate an RSA private key and public key pair in PEM format, and store them as private.pem and public.pem in the server folder. This private key, and its corresponding public key, will be used to register a DID that will represent the web app. All sign-in requests issued by the website must be signed using this private key. This allows user agents and users to reliably identify the origin of sign-in requests, helping keep things secure.

openssl genrsa -out server/private.pem 2048
openssl rsa -in server/private.pem -outform PEM -pubout -out server/public.pem

Now, run the DID registration script in the repo's root to register a DID for your website. This script will use the keys you generated above to register a DID for the website. For details on how this script works, read the registration tutorial.

node register.js

Lastly, copy the DID output by this script and replace the serverDid value in server/app.js with your website's new DID.

Important

This tutorial currently uses the test DID method and RSA as a default crypto algorithm. This tutorial is in the process of being migrated to the ion-test method and elliptic curve cryptography. The tutorial will currently not work with ion-test DIDs.

Run the web app

Now you're ready to run the sample web app:

  • Run node server/app.js to start the server.
  • Navigate to localhost:8080 to load the website.
  • Sign in with your DID!

When you trigger a sign-in request, open the chrome extension to approve the sign in request. After approving, you should be signed into the app with your DID!

Code walk through

The remainder of this article will walk through the important parts of the sample code, explaining how DIDs are used for web authentication.

server/index.html

The sample code starts with a very simple web page with a sign-in button:

<html>
<body>
    <button type="button" id="sign-in">Sign in with DID!</button>
    <button type="button" id="sign-out" style="display:none">Sign Out</button>        
    <pre id='display'></pre>
    
    <script>
        var signIn = document.getElementById('sign-in');
        var signOut = document.getElementById('sign-out');
        var display = document.getElementById('display');
        
    </script>
</body>
</html>

When the button is clicked, the web page does three things:

  1. It sends a request to the server to get a signed authentication request.
  2. It then asks the user to sign in by sending the signed authentication request to the user agent via the navigator.did.requestAuthentication API that the chrome extension injects into the page.
  3. It waits for a response from the user agent, and updates the web page UI with the result.
<html>
<body>
    <button type="button" id="sign-in">Sign in with DID!</button>
    <button type="button" id="sign-out" style="display:none">Sign Out</button>        
    <pre id='display'></pre>
    
    <script>
        var signIn = document.getElementById('sign-in');
        var signOut = document.getElementById('sign-out');
        var display = document.getElementById('display');
        
        // on button click, send a sign-in request to the User Agent
        signIn.addEventListener('click', () => {
            display.innerText = 'sending sign-in request';
            
            // Get an auth request from the server to send to user agent
            fetch('/login')
                .then(handleErrors)
                .then(function(response) {
                    response.text().then(function(authRequest) {
                        // Trigger the DID user agent to prompt the user to sign-in
                        navigator.did.requestAuthentication(authRequest)
                            .then(response => {
                        
                                // Call /check-session to check 
                                // that we are signed in with DID
                                checkSignInStatus();
                        
                            }).catch(error => {
                                display.innerText = 'Error returned from user agent: ' + error;
                            })
                    })
                }).catch(function () {
                    display.innerText = 'Error getting auth request from server.';
                })
        })
    </script>
</body>
</html>

This is all that is necessary to add DID based sign-in to your web page. The entire index.html file is available in the complete sample. Now let's move on to the server.

server/app.js

The server implementation begins by loading the DIF authentication packages, reading the website's private key from disk, and instantiating an Authentication class. Most of the code here is used to convert keys into the expected formats.

var session = require('express-session')
var bodyParser = require('body-parser')
var fetch = require('node-fetch')

//////////////// DID specific packages
var didCommon = require('@decentralized-identity/did-common-typescript');
var didAuth = require('@decentralized-identity/did-auth-jose')
var RsaPrivateKey = require('@decentralized-identity/did-auth-jose/dist/lib/crypto/rsa/RsaPrivateKey');

///////////// Constants
const discovery_endpoint = "https://beta.discover.did.microsoft.com/";
const serverKeyId = "testKey";
const serverDid = "did:test:69921030-2148-40b0-b808-e5b30fd007ff"; //PUT IN YOUR REGISTERED DID HERE

///////////// Load the server's DID private key
// Convert PEM RSA key into JWK with correct key ID
const privatePem = fs.readFileSync(path.resolve(__dirname, './private.pem'), 'ascii');
const jwk = pemJwk.pem2jwk(privatePem);
jwk.kid = `${serverDid}#${serverKeyId}`;

// create RsaPrivateKey instance
const key = new RsaPrivateKey.default({
    id: jwk.kid,
    type: 'RsaVerificationKey2018',
    publicKeyJwk: jwk
});

// convert key into dict of keys
const keys = {};
keys[jwk.kid] = key;

//////////// DID auth module setup
// create Authentication class
const resolver = new didCommon.HttpResolver({

The first route the server exposes is the /login route, which receives AJAX requests from the browser and returns a signed DID authentication request using the Authentication class. The browser will ask for a new signed request each time the user clicks the sign-in button.

})

// ajax request to see if the user is signed in
app.get('/check-session', function (req, res) { 
  res.send(`Successfully logged in as ${req.session.did}`)
})

// ajax request to get a DID authentication request from the server
app.get('/login', function (req, res) { 

  // generate auth request using OpenID Connect self-issued syntax
  var redirectUrl = req.protocol + '://' + req.get('host') + '/auth-response'
  authRequest = {
    iss: serverDid,
    response_type: 'id_token',
    client_id: redirectUrl,
    scope: 'openid',
    state: undefined,
    nonce: req.session.id,
    claims: undefined
  }

property value description
iss did:test:79cc8f04-0588-4513-b1c6-ae987610c082 The DID of the website issuing the sign-in request
response_type id_token Must be id_token
client_id https://localhost:8080/auth-response In accordance with OpenID Connect self-issued, the redirect URI should be used as the client ID value. This is the location where the authentication response will be returned.
scope openid Must be openid
state 12345 Can be used to pass context from the authentication request to the authentication response. The exact value used here will be included in the resulting authentication response.
nonce 12345 A randomized string that is used to prevent token replay attacks. Its value must be checked during response validation. In the sample, we've used the express-session session ID as a nonce.
claims undefined For future use only.

When the user approves a sign-in request, their user agent will return a DID authentication response to the indicated redirect URI. The server will receive this request at /auth-response and validate it using the Authentication class:

  auth.signAuthenticationRequest(authRequest).then(function (authReqJws) {
    res.send(authReqJws)
  }).catch(function (error) {
    res.status(500).send(error)
  })
})

// ajax request to receive auth response from user agent
// expects incoming response in JWS format
var textParser = bodyParser.text({ type: 'application/jwt' })
app.post('/auth-response', textParser, function (req, res) {

The user's DID is available in the sub claim of the response, which is formatted as a JSON web token (JWT). You can use this value as the user's unique ID across your apps & systems if you wish.

At this point, you have successfully authenticated the user with their DID! But our web app is not yet complete. We now want to establish a session with the user, so that the user remains signed-in as they navigate through our site.

After validating the authentication response, start a session using the express-session NPM package in server/app.js:

  auth.signAuthenticationRequest(authRequest).then(function (authReqJws) {
    res.send(authReqJws)
  }).catch(function (error) {
    res.status(500).send(error)
  })
})

// ajax request to receive auth response from user agent
// expects incoming response in JWS format
var textParser = bodyParser.text({ type: 'application/jwt' })
app.post('/auth-response', textParser, function (req, res) {

  // validate an incoming DID authentication response
  auth.verifyAuthenticationResponse(req.body).then(function(authResponse) {

    // validate the nonce in the returned authentication response
    if (authResponse.nonce != req.session.id)
      throw 'Nonce in auth response did not match nonce in session cookie.'

    // bind the session to the DID included in the response

Then on each request to the server, check for a valid DID-bound session, which will ensure the user must be logged in with their DID to access the site:

    resolver
});

//////////// Main server function
const app = express()
const port = 8080

// Set up cookie based sessions
app.use(session({
  secret: 'cookie-secret-key',
  resave: false,
  saveUninitialized: true
}))

// require sign-in for all routes except 
// home, login, and auth-response
app.use(function (req, res, next) {

  if (['/', '/login', '/auth-response'].indexOf(req.path) >= 0) {
    return next()
  }

Congratulations! You've successfully added DID sign-in to a website using public key credentials that are published using distributed ledger technologies. You're well on your way to building a new wave of apps and services that let users own and control their digital identity.

For the complete code sample, click here.




See something missing? We'd love your feedback and input on the Verifiable Credentials preview. Please contact us. When you use Microsoft DID Services, you agree to the DID Preview Agreement and the Microsoft Privacy Statement.

In This Article
  • Contact us
  • Terms of use
  • Privacy statement
  • © Microsoft 2018