Using Maskinporten Authorisation with the Correspondence Service
On the previous page, we went through how to set up a resource and the versioning requirements for the correspondence client.
We can now proceed to the Maskinporten setup and the application code.
Maskinporten Setup
In order to use the correspondence service, a Maskinporten client with the access to the following scopes is required:
altinn:serviceowner
altinn:correspondence.read
altinn:correspondence.write
To set this up, follow the general steps outlined in the Maskinporten integration guide, with a couple of modifications described below.
The correspondence client uses a new, internal, client to communicate with Maskinporten. Because of this, the configuration object now looks like this:
App/appsettings.json
"MaskinportenSettings": { "authority": "https://[test.]maskinporten.no/", "clientId": "the client id", "jwkBase64": "base64 encoded jwk" }
The correspondence client will automatically find and use the Maskinporten client, and attempt to bind to the default
MaskinportenSettings
configuration path.If you require a different configuration path, you can configure it with the
ConfigureMaskinportenClient
extension method:App/Program.cs
void RegisterCustomAppServices( IServiceCollection services, IConfiguration config, IWebHostEnvironment env ) { services.ConfigureMaskinportenClient( "UniqueMaskinportenSettingsPath" ); }
If you require a custom configuration flow, you can make use of the available configuration delegate:
App/Program.cs
void RegisterCustomAppServices( IServiceCollection services, IConfiguration config, IWebHostEnvironment env ) { services.ConfigureMaskinportenClient(config => { config.Authority = "https://[test.]maskinporten.no/"; config.ClientId = "the client id"; config.JwkBase64 = "base64 encoded jwk"; }); }
Application code
Using the dependency injection framework in .NET, you can inject an ICorrespondenceClient
in your service.
This client can then be used to send correspondences and will be able to automatically handle the Maskinporten authorisation.
When sending a correspondence, there are a wealth of properties available. While only a handful of these are required,
the process of constructing the request itself can be a bit daunting. To assist with this, there is a
CorrespondenceRequestBuilder
interface available.
The example below uses the builder to construct a correspondence request using the most common options: the message itself, a notification to the recipient, and an attached file.
You will find all available options and associated documentation through IntelliSense in your favorite code editor.
Service registration
App/Program.cs
// ...
void RegisterCustomAppServices(
IServiceCollection services,
IConfiguration config,
IWebHostEnvironment env
)
{
services.AddTransient<ITheInterfaceYouAreImplementing, CorrespondenceClientDemo>();
}
Correspondence client implementation
App/CorrespondenceClientDemo.cs
using System;
using System.Threading.Tasks;
using Altinn.App.Core.Features.Correspondence;
using Altinn.App.Core.Features.Correspondence.Builder;
using Altinn.App.Core.Features.Correspondence.Models;
namespace Altinn.App;
internal sealed class CorrespondenceClientDemo(
ICorrespondenceClient correspondenceClient
) : ITheInterfaceYouAreImplementing
{
public async Task<SendCorrespondenceResponse> SendMessage()
{
CorrespondenceAuthorisation authorisation = CorrespondenceAuthorisation.Maskinporten;
CorrespondenceRequest request = CorrespondenceRequestBuilder
.Create()
.WithResourceId("A valid resource registry identifier")
.WithSender("Sender's organisation number")
.WithSendersReference("Sender's arbitrary reference for the correspondence")
.WithRecipient("Recipient's organisation number")
.WithAllowSystemDeleteAfter(DateTime.Now.AddYears(1))
.WithContent(
language: "en",
title: "Hello from .NET 👋🏻",
summary: "The message summary",
body: "The message body with markdown support"
)
.WithNotification(
CorrespondenceNotificationBuilder
.Create()
.WithNotificationTemplate(CorrespondenceNotificationTemplate.CustomMessage)
.WithEmailSubject("New Altinn message")
.WithEmailBody(
"You have a new message in your Altinn inbox, log in to see what's new."
)
.WithSmsBody("Got 📨 in Altinn")
.WithNotificationChannel(CorrespondenceNotificationChannel.EmailPreferred)
)
.WithAttachment(
CorrespondenceAttachmentBuilder
.Create()
.WithFilename("attachment.txt")
.WithName("The attachment 📎")
.WithSendersReference("Sender's arbitrary reference for the attachment")
.WithDataType("text/plain")
.WithData("This is the attachment content"u8.ToArray())
)
.Build();
return await correspondenceClient.Send(
new SendCorrespondencePayload(request, authorisation)
);
}
public async Task<GetCorrespondenceStatusResponse> GetMessageStatus(Guid correspondenceId)
{
return await correspondenceClient.GetStatus(
new GetCorrespondenceStatusPayload(
correspondenceId,
CorrespondenceAuthorisation.Maskinporten
)
);
}
}
Notes on authorisation
In the example above, we are using the CorrespondenceAuthorisation.Maskinporten
enum to indicate that authorisation should
be automatically handled internally with Maskinporten. This is by far the easiest and most convenient authorisation method, but
it’s not the only option available.
If you require custom authorisation logic while sending correspondences, you are able to supply your own delegate for this purpose.
An example of this could be if you for some reason preferred to use the legacy external Maskinporten client.
Both the SendCorrespondencePayload
and GetCorrespondenceStatusPayload
accepts a delegate parameter. The implementation would look something like this:
// ...
new SendCorrespondencePayload(
request,
async () =>
{
TokenResponse maskinportenResponse = await maskinportenService.GetToken(
"base64 encoded jwk",
"test|prod",
"the client id",
"altinn:serviceowner altinn:correspondence.write",
null
);
TokenResponse altinnResponse = await maskinportenService.ExchangeToAltinnToken(
maskinportenResponse,
"test|prod"
);
return JwtToken.Parse(altinnResponse.AccessToken);
}
);