Skip to content

External Login — Google, Microsoft, Apple, GitHub, Facebook, OIDC

Authentication validates the API tokens your app receives. External Login is the other side: the interactive “Sign in with Google” button that gets a user a session in the first place. Granit.Authentication.External wires ASP.NET Core’s social authentication handlers from configuration — you add a provider by editing appsettings, not code, and a startup guard refuses to boot if a configured provider has no handler package.

The core module owns the config binding and the startup guard; each provider ships as its own package so you reference only the handlers you use.

  • DirectoryGranit.Authentication.External/ core — config binding, registry, startup guard
    • Granit.Authentication.External.Google Microsoft.AspNetCore.Authentication.Google
    • Granit.Authentication.External.Microsoft Microsoft Account
    • Granit.Authentication.External.Apple AspNet.Security.OAuth.Apple
    • Granit.Authentication.External.GitHub AspNet.Security.OAuth.GitHub
    • Granit.Authentication.External.Facebook Microsoft.AspNetCore.Authentication.Facebook
    • Granit.Authentication.External.Oidc generic OpenID Connect (Okta, Auth0, …)
Provider TypePackage / moduleASP.NET Core handler
GoogleGranit.Authentication.External.GoogleMicrosoft.AspNetCore.Authentication.Google
Microsoft…External.MicrosoftMicrosoft.AspNetCore.Authentication.MicrosoftAccount
Apple…External.AppleAspNet.Security.OAuth.Apple
GitHub…External.GitHubAspNet.Security.OAuth.GitHub
Facebook…External.FacebookMicrosoft.AspNetCore.Authentication.Facebook
Oidc…External.OidcMicrosoft.AspNetCore.Authentication.OpenIdConnect

Reference the provider packages you need and depend on their modules — the core module is pulled in transitively.

[DependsOn(
typeof(GranitAuthenticationExternalGoogleModule),
typeof(GranitAuthenticationExternalMicrosoftModule))]
public class AppModule : GranitModule { }

Then declare the providers in configuration. A provider is registered only when it appears here with a non-empty ClientId:

appsettings.json
{
"Authentication": {
"External": {
"AutoRegisterExternalUsers": true,
"Providers": [
{
"Type": "Google",
"ClientId": "your-client-id",
"ClientSecret": "your-client-secret",
"DisplayName": "Google",
"Scopes": ["email", "profile"]
},
{
"Type": "Microsoft",
"ClientId": "your-client-id",
"ClientSecret": "your-client-secret"
}
]
}
}
}
FieldRole
TypeProvider kind — Google, Microsoft, Apple, GitHub, Facebook, Oidc
NameScheme name; defaults to Type. Set it to run two instances of one kind (e.g. two Oidc)
ClientId / ClientSecretOAuth credentials — inject via user-secrets, env vars, or Vault, never hardcode
ScopesExtra scopes; the handler default applies when omitted
AuthorityOIDC issuer URL — required for Type: Oidc
CallbackPathOverrides the handler’s default callback (e.g. /signin-google)
DisplayNameHuman label for your login UI
PropertiesProvider-specific extras (Apple keys, GitHub Enterprise URL)

AutoRegisterExternalUsers (default true) provisions a local account on a user’s first external sign-in, so external identities flow straight into Identity and authorization.

sequenceDiagram
    participant U as User
    participant App as Granit app
    participant P as Provider (Google…)
    U->>App: GET /login → click "Sign in with Google"
    App->>P: challenge (redirect, PKCE)
    P->>U: provider consent screen
    U->>P: approve
    P->>App: callback ?code=…  (CallbackPath)
    App->>P: exchange code → tokens
    App->>App: AutoRegisterExternalUsers → local account
    App->>U: session established

To render the buttons, inject IExternalProviderRegistry — it lists the configured providers as ExternalProviderInfo(Name, Type, DisplayName) so the UI only ever offers what is actually wired.

Startup guard — fail fast, not at first login

Section titled “Startup guard — fail fast, not at first login”

The core module validates, at boot, that every provider configured with a non-empty ClientId has a registered handler. Configure Apple but forget to reference Granit.Authentication.External.Apple, and the app throws immediately with a message naming the offending scheme and the package to add — instead of a confusing 404 the first time a user clicks the button.

Apple does not accept a static client secret — it requires a short-lived JWT signed with a P8 key. Granit sets GenerateClientSecret = true and builds it for you; supply the inputs through Properties:

{
"Type": "Apple",
"ClientId": "com.yourcompany.app", // the Apple *Services ID*, not the bundle id
"Properties": {
"TeamId": "ABCDE12345",
"KeyId": "KEY1234567",
"PrivateKey": "-----BEGIN PRIVATE KEY-----\n\n-----END PRIVATE KEY-----"
}
}