# ProviderIcon — the mark on every "Continue with …" and "Link …" button

> The UserAccounts kit draws each sign-in provider's mark on its buttons — "Continue with …" on the sign-in page, "Link …" on the account page — from one mapping, `ProviderIcon`. The GitHub, Google and Microsoft marks are built in; any other provider shows the plain sign-in glyph until the app gives it a mark of its own by deriving `ProviderMarks`.

<!-- id: security-provider-icon · area: security · stability: preview · html: https://osysharp.com/reference/security/provider-icon/ -->

## Summary        {#summary}

Every provider the app offers for signing in ([[security-accounts-kit#providers]]) gets a button on the sign-in page —
**Continue with GitHub** — and one on the account page — **Link GitHub**. Both draw the provider's mark from the same
mapping, so the two pages can never show different logos for one provider:

| provider | mark | where it comes from |
|---|---|---|
| `GitHub` | `Icons.Github` | built in |
| `Google` | `Icons.Google` | built in |
| `Microsoft` | `Icons.Microsoft` | built in |
| any other | `Icons.Login` — the plain sign-in glyph | until the app gives it one |

The three built-in names are matched without regard to case, and the marks are drawn in the current text colour like every icon, so
they follow the app's theme in light and dark. No app draws them; `Icons.Github`, `Icons.Google` and `Icons.Microsoft`
are also there for any page of the app's own.

```osy title="a desk that signs in with its company's own provider, shown with a mark the app chose" test app=security-provider-icon
app Desk {
  use Osysharp.Ui;
  use Osysharp.Identity;
  use Osysharp.Mail;
  use Osysharp.UserAccounts;
  use Osysharp.EmailTemplates;
  use Osysharp.Oauth;
  model "model/**/*.osy";
}

using Osysharp.Identity;
using Osysharp.UserAccounts;

[Role] enum AppRole { Authenticator, Agent }

app.Secrets = [ new Secret("AcmeClientSecret") ];
app.OAuthClients = [
  new OAuthClient("Acme") {
    Provider = OAuthProvider.Oidc, Capabilities = [OAuthCapability.Login],
    Issuer = "https://login.acme.example", ClientId = "desk", ClientSecret = Secret.AcmeClientSecret,
    Scopes = ["openid", "email", "profile"],
  },
];

app.Accounts = new AccountsSetup { AppName = "Acme Desk", SignupRole = AppRole.Agent, Marks = new DeskMarks() };

// The company's own sign-in shows a building; every other provider keeps the kit's mark.
public class DeskMarks : ProviderMarks {
  public override Icons For(string provider) { return provider == "Acme" ? Icons.Building : base.For(provider); }
}

[Route("/")] component Home() { render { Text("The desk"); } }
```

## Signature      {#signature}

```osy syntax
public Icons ProviderIcon(string provider);                       // the one mapping — AccountsSetup.Marks.For(provider)
public class ProviderMarks { public virtual Icons For(string provider); }   // derive and override to add a mark
public class ProviderMark { string Provider; Icons Mark; }        // a provider and its mark, as a page is handed it
public Icons MarkOf(List<ProviderMark> marks, string provider);   // the mark a page draws, from what it was handed
// AccountsSetup.Marks = new ProviderMarks();   AccountsFacts.Marks · ProviderFacts.Marks
```

## Description    {#description}

### How does an app give another provider its mark?   {#own-mark}

Derive `ProviderMarks`, override `For`, and set it as `AccountsSetup.Marks` — as the example does. Answer the
providers you know and hand every other one back to `base.For(provider)`, so GitHub, Google and Microsoft keep their
built-in marks and an unknown one still gets the sign-in glyph rather than a blank square. `provider` is the name the
provider is declared under in `app.OAuthClients` ([OAuth clients (app.OAuthClients)](https://osysharp.com/reference/config/oauth-clients/)) — the same name the button reads.

The mark can be any icon the app has, its own included: put `acme.svg` in `model/icons/` and answer `Icons.Acme`. The
kit's buttons take the app's icon vocabulary, merged with the built-ins ([[ui-icons#as-a-value]]), so there is nothing
to register and the name is checked when the app compiles.

### Where is the mapping read?   {#where}

On the server, like every other part of `AccountsSetup` — the setting never reaches a browser. The facts each page
already fetches carry the answer: the sign-in page's (`AccountsPageFacts().Marks`) and the account page's
(`MyProviders().Marks`) hold every offered provider with its mark, and the buttons draw from those with `MarkOf`. So a
page of the app's own that takes a kit page over ([routes and pages](https://osysharp.com/reference/ui/routing/)) draws the same marks by reading the same facts:

```osy title="a sign-in button of the app's own, with the kit's mark for it" syntax
Button($"Continue with {p}", onPress: () => ContinueWith(p)) { Icon(MarkOf(facts.Marks, p), size: 18); }
```

`ProviderIcon(provider)` answers one provider directly, in a function or a test.

### How do I test it?   {#test}

```osy title="the app's mark, and the kit's for everything else" test app=security-provider-icon
[Test]
void each_provider_shows_its_mark() {
  Assert.Equal(Icons.Building, ProviderIcon("Acme"));
  Assert.Equal(Icons.Github, ProviderIcon("GitHub"));
  Assert.Equal(Icons.Login, ProviderIcon("Okta"));
}

[Test]
void the_sign_in_page_is_handed_each_offered_providers_mark() {
  OfferedSignInProviders.Stub(() => new List<string> { "Acme", "Google" });
  var facts = AccountsPageFacts();
  Assert.Equal(Icons.Building, MarkOf(facts.Marks, "Acme"));
  Assert.Equal(Icons.Google, MarkOf(facts.Marks, "Google"));
}
```

Stub `OfferedSignInProviders` to say which providers are offered, rather than depending on whose client secrets the run
has. A UI test sees the button by its words — `Assert.Visible("Continue with Acme")`; the mark itself is seen in a
photograph (`osy test --pixels` and `Ui.Shot`).

## See also       {#see-also}

- [The UserAccounts kit — sign-in done properly, once](https://osysharp.com/reference/security/accounts-kit/) — signing in with a provider, and linking one from the account page
- [OAuth clients (app.OAuthClients)](https://osysharp.com/reference/config/oauth-clients/) — declaring the providers the buttons are for
- [icons](https://osysharp.com/reference/ui/icons/) — the icon vocabulary a mark comes from, the app's own files included
- [OAuth account completion — Security.VerifyPendingOAuthEmail / LinkOAuthFromPending](https://osysharp.com/reference/security/oauth-completion/) — what the platform hands the kit when a provider sign-in comes back
