VPN | Getting Started
Add your VPN to the device, bring it up and down from code, and know whether it actually connected —
using the IKEv2 client the operating system already ships. The OS runs the tunnel, so it keeps going
after your app is closed and shows up in the system VPN settings like any other. Built on
Shiny.Core, so it runs in any Shiny host: MAUI, console, service, or daemon.
| GitHub | |
| Downloads |
Read this first
Section titled “Read this first”This is not a VPN protocol implementation. It configures the OS’s built-in IKEv2 client — the one behind iOS’s Add VPN Configuration screen, Android’s platform VPN profiles and NetworkManager’s strongSwan plugin. Your server must speak IKEv2 (strongSwan, Libreswan, Windows RRAS, most firewalls and commercial VPN services do).
WireGuard, OpenVPN or a protocol of your own need an Apple packet tunnel extension
(NEPacketTunnelProvider) or an Android VpnService that implements the protocol and moves every
packet itself. This package does not do that.
Each app owns one profile — that is how both phone platforms work — so saving again replaces it.
What each platform can do
Section titled “What each platform can do”Every manager publishes a VpnCapabilities flags property, and anything missing throws
VpnNotSupportedException naming the limit.
| iOS / Catalyst / macOS | Android 13+ | Android 11–12 | Linux | |
|---|---|---|---|---|
| Save, connect, disconnect, remove | ✅ | ✅ | ✅ | ✅ |
Status and StatusChanged |
✅ | ✅ | ❌ | ✅ |
| Shared secret (PSK) | ✅ | ✅ | ✅ | ✅ |
| Username / password (EAP) | ✅ | ✅ | ✅ | ✅ |
| Client certificate | ✅ | ✅ | ✅ | ❌ |
| Private CA for the server certificate | ❌ | ✅ | ✅ | ✅ |
| Remote identity other than the server address | ✅ | ❌ | ❌ | ✅ |
Android below 11 has no API for this at all. On Android 11 and 12 the OS cannot report the state, so
Connect returns as soon as the start is accepted and GetStatus() returns Unknown. There is no
Windows target.
Step 1 — Add the marketplace:
claude plugin marketplace add shinyorg/skillsStep 2 — Install the plugin:
claude plugin install shiny@shinyOne plugin installs all 48 Shiny skills. Your agent loads only the skill relevant to what you're building, so there's no cost to having them all available.
Step 1 — Add the marketplace:
copilot plugin marketplace add https://github.com/shinyorg/skillsStep 2 — Install the plugin:
copilot plugin install shiny@shinyOne plugin installs all 48 Shiny skills. Your agent loads only the skill relevant to what you're building, so there's no cost to having them all available.
dotnet add package Shiny.Net.VpnOn Linux, install the NetworkManager-backed package instead:
dotnet add package Shiny.Net.Vpn.Linuxusing Shiny;
builder.Services.AddVpn(); // IVpnManagerEach platform needs a little setup — the Apple Personal VPN entitlement, or the strongSwan plugin on Linux. See Platform Setup.
Save a profile
Section titled “Save a profile”using Shiny.Net.Vpn;
public class VpnSetup(IVpnManager vpn){ public async Task<bool> Install(string secret) { var result = await vpn.SaveProfile(new VpnProfile { Name = "Contoso VPN", ServerAddress = "vpn.contoso.com", LocalIdentifier = "phone@contoso.com", Authentication = VpnAuthentication.SharedSecret(secret) });
// Denied = the user declined the system prompt; nothing was saved return result == AccessState.Available; }}The first save shows the OS’s own consent prompt, so call it from the foreground in response to the user. Other ways to authenticate:
VpnAuthentication.UsernamePassword("allan", password); // IKEv2 EAP-MSCHAPv2VpnAuthentication.Certificate(File.ReadAllBytes("me.p12"), "password"); // PKCS#12 client certificate| Property | |
|---|---|
Name |
Shown in the system VPN settings. Android lists the profile under your app’s name instead. |
ServerAddress |
Host name or IP address of the server. |
Authentication |
Shared secret, username/password or certificate. |
RemoteIdentifier |
The server’s IKE ID. Defaults to ServerAddress; a different value is not possible on Android. |
LocalIdentifier |
This device’s IKE ID. Android needs it for shared secret and certificate profiles. |
ServerRootCertificate |
DER or PEM CA certificate when the server’s certificate comes from a private CA. Not possible on Apple — install the CA through a configuration profile instead. |
Connect and disconnect
Section titled “Connect and disconnect”try{ await vpn.Connect(TimeSpan.FromSeconds(20)); // returns once it is connected}catch (VpnConnectionException ex){ // the OS's reason, where it gives one: credentials rejected, server not responding, ... ShowError(ex.Message);}
await vpn.Disconnect();Connect throws VpnPermissionException when there is no profile to start — including when the user
deleted it from Settings.
Watch the state
Section titled “Watch the state”The user can switch the VPN off in Settings, and the OS reconnects it on its own after a network change, so read the state rather than remembering it:
var state = await vpn.GetStatus();
vpn.StatusChanged += (_, state) =>{ // Unknown, NotConfigured, Disconnected, Connecting, Connected, Reconnecting, Disconnecting MainThread.BeginInvokeOnMainThread(() => this.Status = state.ToString());};Subscribing delivers the current state once, then each change.
Remove the profile
Section titled “Remove the profile”await vpn.RemoveProfile(); // disconnects and deletes the profile and its stored secretsRead on: Platform Setup


