After the role has been selected the authentication process continues by confirming the attributes. For that purpose the
AuthenticateRequest contains an AuthenticationPersonalInformation property named info. This object carries
information about the user's attributes that must be confirmed for the authentication, for example the email address,
given name, family name etc. This process is needed for services that request these attributes directly from the
XignIn-Manager. However, the XignIn-Manager is a complex system that can be configured in many ways and the storage of
user data is one of the components that is variable. It is possible to create services that are only used for
authentication purposes and do not request any user data from the XignIn-Manager. For example, a third-party identity
manager such as a Keycloak can be used to manage the user data. In this case, the XignIn-Manager only provides a
mapping id with which the application can fetch the user data after a successful authentication from the third-party
identity manager. Consequently, the service configured within the XignIn-Manager has no knowledge about which attributes
are requested from the user and therefore no attributes must be confirmed within the SDK. This said, the confirmation of
user attributes can be skipped, if the implementing app only supports services that do not require user data directly
from the XignIn-Manager.
Note: The XignIn-Manager of the Servicekonto.NRW does not hold any user data directly. Therefore, the confirmation of user attributes within the SDK can be skipped.
The AuthenticationPersonalInformation contains properties of the type AuthenticationInformation named after each
possible attribute, e.g. givenName, email, birthdate etc. Each of these properties must be treated in a similar
manner. First, if a service does not request personal information of a specific type the corresponding property
is nil(null) and does not need further handling. Otherwise, the AuthenticationInformation determines how the
attribute must be handled in detail. Generally the XignIn-Manager supports optional attributes, i.e. attributes that the
service does not require for core functionalities but for optional additional features the user can opt in to. However,
optional attributes are not yet fully supported and can be ignored for now. Required attributes on the other hand must
always be confirmed, otherwise the authentication can not be completed. If the user does not want to confirm these, the
process SHOULD be aborted.
Services may require personal information the user has not yet provided. Since authentication processes should not be
needed to be canceled in this case, XignIn supports adding missing information within the process. Therefore, the
property isDataMissing must be checked. If personal information are missing the user must be asked for the
corresponding data. The resulting input can be set by invoking setValue(value:). The accepted type is determined by
the attribute, e.g. for the givenName a String can be passed and for the birthdate an instance
of Date (Instant). The SDK roughly validates the input and returns an enum which indicates whether the value was
accepted or which error occurred. Depending on the attribute, a different implementation is returned, however all share
a case named valid (VALID). In this case the supplied value was accepted. Any other case signals an invalid input
and the stored value is reset to nil (null). For easier implementation of the UI the attributes validator can be
accessed directly via the property validator and may provide additional parameters like minLength or maxLength.
Contrary to missing personal information, an attribute can also have several values. In this case the user must choose
which value should be supplied to the service. For data privacy reasons the concrete values to choose from are not
returned, instead aliases are used which can be checked via the property aliases. This property is a set
of PersonalInformationAlias. If more than one entry is present a selection must be made. An instance
of PersonalInformationAlias contains the displayable alias as String the user has configured for the value and a
flag named isPrimary. The flag can be used to offer the user an easier selection process for example by prefilling the
UI component used for the selection with the primary alias. The chosen alias can be set by invoking setSelection on
the AuthenticationInformation instance. This function returns the adopted value or nil (null), in case an alias
was passed which is not part of the available selection.
Note: In most cases a selection of an attribute value only needs to be done once per service. After the first successful authentication against a service the selection is saved by the XignIn-Manager. All further authentications against the same service will only request the previous selected values. If that is the case, the
AuthenticationInformation.aliasesset contains only one entry, which will be selected automatically.
If neither personal information are missing nor an alias must be selected the attribute simply needs to be confirmed. For required attributes no further steps are necessary at this point in the authentication process. However, it is strongly recommended that the user is presented with at least some kind of UI that indicates what personal information will be transmitted to the service after the authentication process is completed.
After all stored properties within the AuthenticationPersonalInformation have been handled, the user information can
be prepared for signing. This can be achieved by
invoking Authenticator.prepareAuthenticationUserInformation(:) or its synchronous counterpart with
the AuthenticateRequest. The result of that function is an enum (sealed class) of the
type PrepareAuthenticationUserInformationResult with two cases (concrete implementations) failure and success.
The failure case indicates that at least one of the requested attributes is erroneous. In order to correct the errors
the previous filled out AuthenticateRequest with updated error information is returned. For that purpose
every AuthenticationInformation property within the AuthenticationPersonalInformation contains a field named error
. This field is of the type AuthenticationInformationErrorCode which is an enum describing the error. The
case none (NONE) indicates that the attribute is errorless and does not need to be handled (again). Any other case
however is an error state and must be handled according to its documentation, which mostly leads to displaying the error
to the user and request a new input. The new input can be supplied to the AuthenticationInformation by the same means
as initially discussed. After all errors haven be corrected the process can be repeated by
invoking Authenticator.prepareAuthenticationUserInformationSynchronous(:) again with the AuthenticateRequest.
Note: The correction of errors cannot be repeated indefinitely. After a certain number of retries a
XignSdkError(XignSdkException) is raised, which cancels the current authentication process.
In the success case an instance of AuthenticationUserInformation is returned, with which the process can be
continued. The following example illustrates how the discussed confirmation of user attributes may look like in code:
private func confirmAuthenticationAttributes(
request: AuthenticateRequest
) throws {
let info: AuthenticationPersonalInformation = request.info
// The info object contains information about the requested attributes of
// the user that must be confirmed. If attribute data are missing, they must
// be updated within this object. The next step is only relevant if the services
// or XignIn-Manager you are targeting manages user information, otherwise it can be
// skipped.
// The following example shows how `AuthenticationInformation` can be handled using
// the attribute `givenName`:
// Firstly check if the property is not `nil`. If it is `nil`, it is not needed
// and can be skipped.
if let infoGivenName: AuthenticationInformation<String, NameValidator> = info.givenName {
// Secondly check if it is required. If not, it can be skipped.
if infoGivenName.isRequired {
// Thirdly check if data are missing.
// Note: Some attributes can't be supplied during an authentication as
// they need some kind of verification.
if infoGivenName.isDataMissing {
var isInputValid: Bool = false
var errorText: String? = nil
while !isInputValid {
// Query the user for input.
let suppliedGivenName: String = YourImplementation.askTheUserForData(
minLength: infoGivenName.validator.minLength,
maxLength: infoGivenName.validator.maxLength,
errorText: errorText
)
// Try setting the given input to the object.
let validationResult: NameValidator.ResultCode = infoGivenName
.setValue(value: suppliedGivenName)
// Check if the input was accepted by the object.
// Note: If the input is not valid, the input within the object is cleared.
switch validationResult {
case .valid:
// everything is fine, continue the process.
isInputValid = true
case .empty:
// The input is `nil` or empty; display an error and repeat.
errorText = "The input must not be empty."
case .tooShort:
// The input is too short. Note for most fields the min length is
// only 1 character.
errorText = """
The input must be at least \
\(infoGivenName.validator.minLength) characters long.
"""
case .tooLong:
// The input is too long.
errorText = """
The input can't be longer than \
\(infoGivenName.validator.maxLength) characters.
"""
}
}
}
// If data are not missing, check if alias are available for selection.
// Note: If only one alias is available, it is set automatically.
else if infoGivenName.aliases.count > 1 {
// Let the user select an alias.
let alias: PersonalInformationAlias = YourImplementation
.displayAliasSelection(infoGivenName.aliases)
// Supply the selected alias to `infoGivenName` object.
if infoGivenName.setSelection(alias) == nil {
// This function returns the set alias. It returns nil in case `nil` is passed or
// an alias that is not part of the given selection `infoGivenName.aliases`. This
// is only relevant for development and can be ignored during production, if
// implemented correctly.
}
} else {
// In this case, the attribute is required but does not need further input. However,
// it is recommended to at least notify the user that the service will receive
// this piece of information.
}
} else {
// Currently, there are no optional attributes. Therefore, this case
// can be left empty.
}
}
// Repeat the code form above in a similar matter for every other property of the `info`
// variable. The following list show all other properties that must be handled:
info.username; info.nickname; info.familyName; info.birthdate; info.address; info.nationality
info.placeOfBirth; info.gender; info.salutation; info.email; info.phoneNumber
// Submit the previous supplemented `AuthenticateRequest` object to the XignIn-Manager.
let result: PrepareAuthenticationUserInformationResult =
try XignSdk.shared.authenticator.prepareAuthenticationUserInformationSynchronous(
request: request
)
let userInformation: AuthenticationUserInformation
switch result {
case .failure(let returnedAuthenticateRequest):
// Correct the errors contained in the `returnedAuthenticateRequest` and repeat the
// preparation.
// The following example shows how errors can be checked using the attribute `givenName`:
let infoGivenNameOpt: AuthenticationInformation<String, NameValidator>? =
returnedAuthenticateRequest.info.givenName
if let infoGivenName = infoGivenNameOpt {
let error: AuthenticationInformationErrorCode = infoGivenName.error
switch error {
case .none:
// no error
break
case .invalid:
// Indicates that the data provided for this attribute are in an invalid format.
break
case .notAvailable:
// Indicates that attribute data was supplied, although it has not been required.
break
case .dataMissing:
// Indicates that data for this attribute must be supplied but has not been supplied.
break
case .noAliasSelected:
// Indicates that and aliases must be selected for this attribute but none was selected.
break
}
}
// After identifying all errors contained in the `returnedAuthenticateRequest`, present them
// to the user, request new input to fix the errors and then repeat the process.
// Note: This process is simplified in this example by invoking the current function again.
try confirmAuthenticationAttributes(request: returnedAuthenticateRequest)
return
case .success(let infos):
userInformation = infos
}
// After supplying all needed data the authentication can be continued by signing to data.
// Note: This is a call to the next documentation example function that illustrates the singing.
try signAuthentication(userInformation: userInformation)
}
private fun confirmAuthenticationAttributes(
request: AuthenticateRequest
) {
val info: AuthenticationPersonalInformation = request.info
// The info object contains information about the requested attributes of
// the user that must be confirmed. If attribute data are missing, they must
// be updated within this object. The next step is only relevant if the services
// or XignIn-Manager you are targeting manages user information, otherwise it can be
// skipped.
// The following example shows how `AuthenticationInformation` can be handled using
// the attribute `givenName`:
// Firstly check if the property is not `nil`. If it is `nil`, it is not needed
// and can be skipped.
val infoGivenName = info.givenName
if (infoGivenName != null) {
// Secondly check if it is required. If not, it can be skipped.
if (infoGivenName.isRequired) {
// Thirdly check if data are missing.
// Note: Some attributes can't be supplied during an authentication as
// they need some kind of verification.
if (infoGivenName.isDataMissing) {
var isInputValid: Boolean = false
var errorText: String? = null
while (!isInputValid) {
// Query the user for input.
val suppliedGivenName: String = YourImplementation.askTheUserForData(
minLength = infoGivenName.validator.minLength,
maxLength = infoGivenName.validator.maxLength,
errorText = errorText
)
// Try setting the given input to the object.
val validationResult: NameValidator.ErrorCode = infoGivenName
.setValue(value = suppliedGivenName)
// Check if the input was accepted by the object.
// Note: If the input is not valid, the input within the object is cleared.
when (validationResult) {
NameValidator.ErrorCode.VALID -> {
// everything is fine, continue the process.
isInputValid = true
}
NameValidator.ErrorCode.EMPTY -> {
// The input is `null` or empty; display an error and repeat.
errorText = "The input must not be empty."
}
NameValidator.ErrorCode.TOO_SHORT -> {
// The input is too short. Note for most fields the min length is
// only 1 character.
errorText = """
The input must be at least
${infoGivenName.validator.minLength} characters long.
""".trimIndent()
}
NameValidator.ErrorCode.TOO_LONG -> {
// The input is too long.
errorText = """
The input can't be longer than
${infoGivenName.validator.maxLength} characters.
""".trimIndent()
}
}
}
}
// If data are not missing, check if alias are available for selection.
// Note: If only one alias is available, it is set automatically.
else if (infoGivenName.aliases.size > 1) {
// Let the user select an alias.
val alias: PersonalInformationAlias = YourImplementation
.displayAliasSelection(infoGivenName.aliases)
// Supply the selected alias to `infoGivenName` object.
if (infoGivenName.setSelection(alias) == null) {
// This function returns the set alias. It returns `null` in case `null` is passed
// or an alias that is not part of the given selection `infoGivenName.aliases`.
// This is only relevant for development and can be ignored during production, if
// implemented correctly.
}
} else {
// In this case, the attribute is required but does not need further input. However,
// it is recommended to at least notify the user that the service will receive
// this piece of information.
}
} else {
// Currently, there are no optional attributes. Therefore, this case
// can be left empty.
}
}
// Repeat the code form above in a similar matter for every other property of the `info`
// variable. The following list show all other properties that must be handled:
info.username; info.nickname; info.familyName; info.birthdate; info.address; info.nationality
info.placeOfBirth; info.gender; info.salutation; info.email; info.phoneNumber
// Submit the previous supplemented `AuthenticateRequest` object to the XignIn-Manager.
val result: PrepareAuthenticationUserInformationResult =
XignSdk.shared.authenticator.prepareAuthenticationUserInformationSynchronous(
request = request
)
val userInformation: AuthenticationUserInformation
when (result) {
is PrepareAuthenticationUserInformationResult.Failure -> {
val returnedAuthenticateRequest: AuthenticateRequest = result.request
// Correct the errors contained in the `returnedAuthenticateRequest` and repeat the
// preparation.
// The following example shows how errors can be checked using the attribute `givenName`:
val infoGivenName: AuthenticationInformation<String, NameValidator.ErrorCode, NameValidator>? =
returnedAuthenticateRequest.info.givenName
if (infoGivenName != null) {
val error: AuthenticationInformation.ErrorCode = infoGivenName.error
when (error) {
AuthenticationInformation.ErrorCode.NONE -> {
// no error
}
AuthenticationInformation.ErrorCode.INVALID -> {
// Indicates that the data provided for this attribute are in an invalid format.
}
AuthenticationInformation.ErrorCode.NOT_AVAILABLE -> {
// Indicates that attribute data was supplied, although it has not been required.
}
AuthenticationInformation.ErrorCode.DATA_MISSING -> {
// Indicates that data for this attribute must be supplied but has not been supplied.
}
AuthenticationInformation.ErrorCode.NO_ALIAS_SELECTED -> {
// Indicates that and aliases must be selected for this attribute but none was selected.
}
}
}
// After identifying all errors contained in the `returnedAuthenticateRequest`, present them
// to the user, request new input to fix the errors and then repeat the process.
// Note: This process is simplified in this example by invoking the current function again.
confirmAuthenticationAttributes(request = returnedAuthenticateRequest)
return
}
is PrepareAuthenticationUserInformationResult.Success -> {
userInformation = result.userInformation
}
}
// After supplying all needed data the authentication can be continued by signing to data.
// Note: This is a call to the next documentation example function that illustrates the singing.
signAuthentication(userInformation = userInformation)
}
After all missing attributes have been supplied, selected and the user has consented to the transfer the authentication process can be continued by singing the consent with all authenticators required by the server as described in the chapter: