This document outlines how a client, which has been granted the appropriate permissions, can make use of OIDC signing support and optionally the SignDoc Resource Server.
Introduction
OIDC signing is an extension to the authorization flow in BankID OIDC. Adding a scope, a signorder reference or a text to be signed in a query parameter and then calling the authorize endpoint, a BankID signing will be performed. The actual signing will be done using the ordinary BankID WebClient or using BankID on mobile. The result of the signing will be available either as a claim in the id_token or as a json object requested from the SignDoc resource server.
Flows
Below [signdoc-baseurl] means the baseurl as given by the .well-known/openid-configuration endpoint.
Kind of flow is independent of choosing BankID WebClient or BankID on Mobile signing. Due to limitations on mobile there are restrictions on type of signing etc.
Simplified flow
One authorize request is sent to the OIDC server, result of signing in id_token.
[authorize_endpoint]... &scope=sign&sign_txt=base64encoded(utf8-string)
result is placed in the id_token as the claim sign_result which contains the enduser and merchant basic-signatures and a hash over the received text.
Full flow
Three requests, one for uploading to the SignDoc server, one for authorizing and signing, one for downloading the result from the SignDoc server.
Merchant uploads a signing order
POST [signdoc-baseurl]/signdoc
header: access-token with scope signdoc/read_write
body: document(s) to be signed, signing parameters, result specification
result: sign_id in json
GET [authorize_endpoint]....&scope=sign&sign_id=[sign_id from upload]
Merchant downloads the signing result
DELETE [signdoc-baseurl]/signdoc?sign_id=[sign_id from upload]
header: access-token with scope signdoc/read_write
result: json body containing result
How to get the url to the SignDoc Resource Server
The Url to access the SignDoc resource server is found in .well-known/openid-configuration result as signdoc-baseurl. The merchant should add /signdoc to the base-url.
How to get the access token to use in the Full flow, i.e. granting access to the SignDoc server
To get an access token with the correct content, first of all the OIDC client must have been granted the correct accesses,
Then send a request to the token endpoint using client_credential grant and scope=signdoc/read_write, and the access token is returned.
An example:
POST / auth/realms/current/protocol/openid-connect/token
HTTP/1.1
Host: oidc-current.bankidapis.no
Content-Type: application/x-www-form-urlencoded
Authorization: Basic b2lkYy10ZXN0Y2xpZW50OmVmMWE4ZWM2LTUwODctNDQ0Yy04NGJlLTU0YTYxZjg4MTIyZQ==
grant_type=client_credentials&scope=signdoc%2Fread_write
|
The response looks like
{
"access_token" : "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJZQjR1R0JDLU5yeFJjamhTU3BaUVJwRzFiRUd6U0huVE0zeHJRTkxabU80In0.eyJqdGkiOiIwY2QwMWY5YS1mMTgxLTRmMTAtYmVlYS1iMjRiYjcyOWU0Y2YiLCJleHAiOjE1MzM3MzkyNTEsIm5iZiI6MCwiaWF0IjoxNTMzNzM4OTUxLCJpc3MiOiJodHRwczovL3Byb3RvdHlwZS5iYW5raWRub3JnZS5uby9hdXRoLWRldi9yZWFsbXMvRGV2ZWxvcCIsImF1ZCI6WyJnZW5lcmljLXJlc291cmNlLXNlcnZlciIsInNpZ25kb2MiLCJmcmF1ZC1kYXRhLXJzIl0sInN1YiI6ImZjY2VlOTM2LTFiNGEtNGNmNC1hNWU4LThkOWYyZTljNThiMiIsInR5cCI6IkJlYXJlciIsImF6cCI6Im9pZGMtdGVzdGNsaWVudCIsImF1dGhfdGltZSI6MCwic2Vzc2lvbl9zdGF0ZSI6IjI3ZTY5NDc2LTRjZjktNGFkYS1hZjhjLWNkOTNjNjhmYzBlZiIsImFjciI6IjEiLCJhbGxvd2VkLW9yaWdpbnMiOltdLCJyZXNvdXJjZV9hY2Nlc3MiOnsiZ2VuZXJpYy1yZXNvdXJjZS1zZXJ2ZXIiOnsicm9sZXMiOlsicm9sZTEiXX0sInNpZ25kb2MiOnsicm9sZXMiOlsicmVhZF93cml0ZSJdfSwiZnJhdWQtZGF0YS1ycyI6eyJyb2xlcyI6WyJHZXRTZWN1cml0eURhdGEiXX19LCJjbGllbnRJZCI6Im9pZGMtdGVzdGNsaWVudCIsImNsaWVudEhvc3QiOiIxNzIuMTYuMC4xNTgiLCJyZXNvdXJjZV9jbGFpbXMiOnt9LCJjbGllbnRBZGRyZXNzIjoiMTcyLjE2LjAuMTU4In0.OOR2RsNEdQUDWL-wpQY7qjNnFucv4zLeQoL8mLVcSggOkrGspQ7z1h-ZZjDzVx294zoJpB0rDVrYgLAlugfQ0z-dqFZPvwUc1vMg4OFeYc4W1rR5L8WEMdprm-TbW6f8lnOGIfROZuijwzyeLZsQ2rKtg13KHGdvlJbeue6PlAVM89SFerFbfoJHpN_AhvATpGsN6GAniwGEgJtD0blCLcC3PQKRJfMUa9ZXo2NjVVtLocjQEwiMAu4EnWRTupv9cmhmFolSDZV_7jYs72KMnn025KroMMvBHL538r8wMztByiT45xMOZHRTbClM_Bwd7zjVdcTdaQp4OuE5vVWIMg" ,
"expires_in" : 300,
"refresh_expires_in" : 3600,
"refresh_token" : "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJZQjR1R0JDLU5yeFJjamhTU3BaUVJwRzFiRUd6U0huVE0zeHJRTkxabU80In0.eyJqdGkiOiIwNWJiNWM5YS1jNmRkLTQ5ZDYtOGEyYS1jZTRlYTFkZTE0NGYiLCJleHAiOjE1MzM3NDI1NTEsIm5iZiI6MCwiaWF0IjoxNTMzNzM4OTUxLCJpc3MiOiJodHRwczovL3Byb3RvdHlwZS5iYW5raWRub3JnZS5uby9hdXRoLWRldi9yZWFsbXMvRGV2ZWxvcCIsImF1ZCI6WyJnZW5lcmljLXJlc291cmNlLXNlcnZlciIsInNpZ25kb2MiLCJmcmF1ZC1kYXRhLXJzIl0sInN1YiI6ImZjY2VlOTM2LTFiNGEtNGNmNC1hNWU4LThkOWYyZTljNThiMiIsInR5cCI6IlJlZnJlc2giLCJhenAiOiJvaWRjLXRlc3RjbGllbnQiLCJhdXRoX3RpbWUiOjAsInNlc3Npb25fc3RhdGUiOiIyN2U2OTQ3Ni00Y2Y5LTRhZGEtYWY4Yy1jZDkzYzY4ZmMwZWYiLCJyZXNvdXJjZV9hY2Nlc3MiOnsiZ2VuZXJpYy1yZXNvdXJjZS1zZXJ2ZXIiOnsicm9sZXMiOlsicm9sZTEiXX0sInNpZ25kb2MiOnsicm9sZXMiOlsicmVhZF93cml0ZSJdfSwiZnJhdWQtZGF0YS1ycyI6eyJyb2xlcyI6WyJHZXRTZWN1cml0eURhdGEiXX19fQ.Sppv8D8NobOrMabWAFL4wknQlHKC4CINeDvDVPPL0zLqRquQZ0x_Uwm9Z-KaUV5jsPuVXtt-jduBXhszptupLyhRVrY2v1ASomdvnud-5sFia042VkqqQTjg47u8n1-p9hjie1qYdXMZp504girUOV6DDPL8Wr5rcC2HBS9oHhXBwKOGZQUsK1mfTTz9KYBo4x6XbV8XSlJyvnBUSism3Told40UEetWtxS-78ohjoyQENcn-d02TTsSnyQh-6ZOEPgBtVHovenMi6p4uMlenklcUUefL40GGpMt3gUp7Hk0jaWPTBU363mjFhrrIrAKgFoztYuwt-M0cPmMkdkQXQ" ,
"token_type" : "bearer" ,
"not-before-policy" : 1520416620,
"session_state" : "27e69476-4cf9-4ada-af8c-cd93c68fc0ef"
}
|
Looking in the internals of the access_token, this reveals that the "aud" claim contains "signdoc" and that the "resource_access" claim contains the 'scope' "signdoc/read_write"
{
"jti" : "0cd01f9a-f181-4f10-beea-b24bb729e4cf" ,
"exp" : 1533739251,
"nbf" : 0,
"iat" : 1533738951,
"iss" : "https://oidc-current.bankidapis.no/auth/realms/current" ,
"aud" : [
"generic-resource-server" ,
"signdoc" ,
"fraud-data-rs"
],
"sub" : "fccee936-1b4a-4cf4-a5e8-8d9f2e9c58b2" ,
"typ" : "Bearer" ,
"azp" : "oidc-testclient" ,
"auth_time" : 0,
"session_state" : "27e69476-4cf9-4ada-af8c-cd93c68fc0ef" ,
"acr" : "1" ,
"allowed-origins" : [],
"resource_access" : {
"generic-resource-server" : {
"roles" : [
"role1"
]
},
"signdoc" : {
"roles" : [
"read_write"
]
},
"fraud-data-rs" : {
"roles" : [
"GetSecurityData"
]
}
},
"clientId" : "oidc-testclient" ,
"clientHost" : "172.16.0.158" ,
"resource_claims" : {},
"clientAddress" : "172.16.0.158"
}
|
Selection of BankID on Mobile or BankID WebClient
The BankID WebClient is by default selected when scope sign is given.
In order to use BankID on Mobile, the login_hint=BIM[:[phoneNumber][:birthDate]] must be given in the authorize request. [...] means optional values. Phonenumber is 8 digits, birthDate is ddmmyy.
To preset nnin in the BankID WebClient set login_hint=[BID]:nnin where nnin is 11 digits. The BID may be dropped.
Full flow API description
The full flow starts by one request where the OIDC client uploads documents, signing properties and a wanted result specification to the SignDoc resource server. The return from this request is a sign_id which is used as a query parameter in the authorize request. When the user has finished signing, the OIDC client queries the result from the SignDoc resource server.
Documented here are the requests for accessing the SignDoc Resource server, the URI is given from the .well-known endpoint. The authorize request is documented above.
Action | Type | Headers | Body or query | Status and result examples |
---|
Upload signing order | POST | authorization: Bearer access_token Content-type: application/json | Body: a Json object specifying the signing to be performed { "signProperties" :
{ "orderName" : "..." ,
"documentDisplayMode" : "interior" | "window" | "overlay" ,
"showConfirmation" : "true" | "false" ,
"showUnderstanding" : "true" | "false" ,
"timeoutSeconds" : "12345" ,
},
"documents" :
[
{
"description" : "..."
"text" : "..." ,
"pdf" : " base64Encoded(pdfbytes) " ,
"xml" : "xml as string to be used together with the xsl" ,
"xsl" : "xml stylesheet"
}, more documents ...
],
"resultContent" :
[ "basicSignature" ,
"documentHash" ,
"sdo"
]
}
|
| 201 CREATED if all is ok { "sign_id": "6be61b7e-e137-4c68-a396-5377ca76a1dd" } 400 BAD request and a message if something missing { "message": "Unknown result specifiers: '[sdoo]'" } 403 FORBIDDEN if missing or invalid access_token { "message": "AccessToken is invalid" } |
Check status | GET | authorization: Bearer access_token | query: ?sign_id=value of signid from Upload signing order
| 200 OK if all is ok { "orderState": "ORDER_RECEIVED" } Possible results are ORDER_RECEIVED, GRABBED_BY_IDP, USER_SIGNING, FAILED, CANCELLED or SIGN_COMPLETED 204 NO CONTENT if sign_id not found or sign_id is owned by another client no body returned (remark timeout is 90 sec before starting signing) 400 BAD request and a message if something missing { "message": "signId should not be empty" } 403 FORBIDDEN if missing or invalid access token { "message": "AccessToken is invalid" } |
Download result and cleanup When this one is called, the session associated to the sign_id is removed | DELETE | authorization: Bearer access_token | query: ?sign_id=value of signid from Upload signing order. Response content is dependent on the resultContent value in registration of the signing order and the state of the order. No matter the order state, this method returns the current state and removes the session. Below is a maximized result. If the orderState is not "SIGN_COMPLETED", the entries specified by "resultContent" is absent. | 204 NO CONTENT if sign_id not found or sign_id is owned by another client no body returned (remark timeout is 90 sec after enduser completed signing) 400 BAD request and a message if something missing { "message": "signId should not be empty" } 403 FORBIDDEN if missing or invalid access token { "message": "AccessToken is invalid" } |
Upload Request examples:
Simplified sign flow expressed like a full flow
The signing order below behaves identically to the simplified flow given by sign_txt=VGhlIHNpbmdfdHh0IGdpdmVuIGlzIHBsYWNlZCBoZXJl
{
"signProperties" : {
"orderName" : "bankID" ,
"showUnderstanding" : "true" ,
"showConfirmation" : "false" ,
"documentDisplayMode" : "interior" ,
"timeoutSeconds" :600
},
"documents" :[
{ "text" : "The sign_txt given is placed here" }
],
"resultContent" :[ "basicSignature" , "documentHash" ]
}
|
Signing of two texts and one xml
{
"signProperties" : {
"orderName" : "test order" ,
"showUnderstanding" : "true" ,
"showConfirmation" : "false" ,
"documentDisplayMode" : "overlay"
},
"resultContent" :[ "sdo" ],
"documents" :[
{ "description" : "my first document to sign" , "text" : "Testing å and ø...." },
{ "text" : "Short version of a document having two linefeeds\n\ndescription when listet in BankID WebCLient should be 'test order - 2" },
{ "description" : "An xslt transformation" ,
"xml" : "<?xml version=\"1.0\" encoding=\"ISO-8859-1\" ?><Persons><Person><Name>Ola Normann</Name><Street>Olaveien 99</Street><Zip>0014</Zip><City>Oslo</City></Person><Person><Name>Kalle</Name><Street>Ibsens veg 30</Street><Zip>9876</Zip><City>Stockholm</City></Person><Person><Name>Kari IngridsDottir</Name><Street>Nedre Sofustrøa 2</Street><Zip>7070</Zip><City>Bosberg</City></Person><Person><Name>Olava Olsen</Name><Street>Øvre Strete 12</Street><Zip>2211</Zip><City>Sørpå</City></Person></Persons>" ,
"xsl" : "<?xml version=\"1.0\" encoding=\"ISO-8859-1\" ?><xsl:stylesheet version=\"1.0\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\"> xsl:output method=\"html\" encoding=\"ISO-8859-1\" doctype-system=\"http://www.w3.org/TR/html4/loose.dtd\" doctype-public=\"-//W3C//DTD HTML 4.01 Transitional//EN\" indent=\"yes\" /><xsl:template match=\"/\"><HTML><BODY><h2>Eksempel på XML/XSL.</h2><p>Under er en liste personer tatt fra XML-en</p><ul><xsl:for-each select=\"Persons/Person\"><xsl:if test=\"0 = position() mod 2\"><li style='color:red'><xsl:value-of select=\"Name\"/>,<xsl:value-of select=\"Street\"/>,<xsl:value-of select=\"Zip\"/>,<xsl:value-of select=\"City\"/></li></xsl:if><xsl:if test=\"1 = position() mod 2\"><li style='color:green'><xsl:value-of select=\"Name\"/>,<xsl:value-of select=\"Street\"/>,<xsl:value-of select=\"Zip\"/>,<xsl:value-of select=\"City\"/></li></xsl:if></xsl:for-each></ul></BODY></HTML></xsl:template></xsl:stylesheet>"
}
]
}
|
sign_id Expiration Time
A sign_id is valid 90 secs (config value) after created, then, while user is signing, the timeout given in the order (300s default), then 90 secs after the user for retrieval by the OIDC client.
BankID on Mobile limitations
Bankid on mobile signs only text, the text must be maximum 118 characters long and has a restricted character set. Legal characters are for now
[0-9] [a-z] [æ] [ø] [å] [A-Z] [Æ] [Ø] [Å] [ ][CR] [LF] [#] [$] [%-&] [(-?] [@] [¡] [£] [¤] [¥] [§] [¿] [Ä] [Ç] [É] [Ñ] [Ö] [Ü] [ß] [à] [ä] [è] [é] [ì] [ñ] [ò] [ö] [ù]
Flags like show understanding and show confirmation do not apply, and english locale on the mobile is not supported.
Utility code for base64 in javascript
function b64EncodeUnicode(str) {
return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g,
function toRawBytes(match, p1) {
return String.fromCharCode( '0x' + p1);
}));
}
function b64DecodeUnicode(str) {
return decodeURIComponent(atob(str).split( '' ).map( function (c) {
return '%' + ( '00' + c.charCodeAt(0).toString(16)).slice(-2);
}).join( '' ));
}
function computeSha256AsB64FromUtf8(str) {
var buffer = new TextEncoder().encode(str);
return crypto.subtle.digest( "SHA-256" , buffer).then( function (hash) {
return btoa(String.fromCharCode.apply( null , new Uint8Array(hash)));
});
}
|