# Att lägga till Google Auth utan ASP.NET identitetsdatabas

<!--category-- ASP.NET, Google Auth -->
<datetime class="hidden">2024-08-05T08:06</datetime>

## Inleiding

I den här appen ville jag lägga till en modellmekanism som möjliggör att logga in för att lägga till kommentarer ( och några administratörsuppgifter ) till appen ♫ . ♫ Jag ville använda Google Auth i detta syfte ♫

Databaser är en kraftfull komponent i vilken applikation som helst men de tillsätter också komplexitet.

[TOC]

## steg

Först måste du sätta upp Google Auth i Google Developer Console. Du kan följa dessa steg [länk](https://developers.google.com/identity/gsi/web/guides/overview) för att få dina detaljer ordnade åt dig Google Client ID och Secret.

När du väl har din Google Client ID och Secret, kan du lägga till dem i din appsettings

```json
    "Auth" :{
"GoogleClientId": "",
"GoogleClientSecret": ""
}
```

Hursomhelst borde man inte kolla in dessa i källkodskontrollen. Istället för den lokala utvecklingen kan man använda hemligheterna i filet

![secrets.png](secrets.png)

Där kan du lägga till din Google Client ID och hemlig. ( lägg märke till att din klient ID inte är identifiserad som ', utan som faktiskt är konfidentiskt.

```json
    "Auth" :{
  "GoogleClientId": "ID",
  "GoogleClientSecret": "CLIENTSECRET"
}
```

## Konfiguration av Google Auth med POCO

Jag använder en modifierad version av Steve Smiths IConfigSection, som nyligen blev berömd av Phil Haack.
Detta är för att undvika de IOptionsgrejer som jag finner lite kluriga ( och sällan behöver eftersom jag nästan aldrig ändrar konfiguration efter utplacering i mina scenarios).

I min gör jag detta som tillåter mig att få sektionens namn från klassen själv:

<details>
<summary>Click to expand</summary>

```csharp


namespace Mostlylucid.Config;

public static class ConfigExtensions {
    public static TConfig ConfigurePOCO<TConfig>(this IServiceCollection services, IConfiguration configuration)
        where TConfig : class, new() {
        if (services == null) throw new ArgumentNullException(nameof(services));
        if (configuration == null) throw new ArgumentNullException(nameof(configuration));
        
        var config = new TConfig();
        configuration.Bind(config);
        services.AddSingleton(config);
        return config;
    }
    
    public static TConfig Configure<TConfig>(this WebApplicationBuilder builder)
        where TConfig : class, IConfigSection, new() {
        var services = builder.Services;
        var configuration = builder.Configuration;
        var sectionName = TConfig.Section;
        return services.ConfigurePOCO<TConfig>(configuration.GetSection(sectionName));
    }
    

    public static TConfig GetConfig<TConfig>(this WebApplicationBuilder builder)
        where TConfig : class, IConfigSection, new() {
        var configuration = builder.Configuration;
        var sectionName = TConfig.Section;
        var section = configuration.GetSection(sectionName).Get<TConfig>();
        return section;
        
    }
    
    public static Dictionary<string, object> GetConfigSection(this IConfiguration configuration, string sectionName) {
        var section = configuration.GetSection(sectionName);
        var result = new Dictionary<string, object>();
        foreach (var child in section.GetChildren()) {
            var key = child.Key;
            var value = child.Value;
            result.Add(key, value);
        }
        
        return result;
    }
    
    public static Dictionary<string, object> GetConfigSection<TConfig>(this WebApplicationBuilder builder)
        where TConfig : class, IConfigSection, new() {
        var configuration = builder.Configuration;
        var sectionName = TConfig.Section;
        return configuration.GetConfigSection(sectionName);
    }
}

public interface IConfigSection {
    public static abstract string Section { get; }
}
```

</details>
Så min Auth ser ut som

```csharp
public class Auth : IConfigSection
{
    public static string Section => "Auth";
    public string GoogleClientId { get; set; }
    public string GoogleClientSecret { get; set; }
    
    public string AdminUserGoogleId { get; set; }
    
}
```

Där jag använder en statisk gränssnittmetod för att få sektionens namn.

Då kan jag göra det här i min uppstartning.

```csharp
var auth = builder.GetConfig<Auth>();
```

Hursomhelst tillbaka till google stuff!

## Programm.cs uppsättning

För att faktiskt lägga till detta

```csharp
services.AddCors(options =>
{
    options.AddPolicy("AllowMostlylucid",
        builder =>
        {
            builder.WithOrigins("https://www.mostlylucid.net")
                .WithOrigins("https://mostlylucid.net")
                .WithOrigins("https://localhost:7240")
                .AllowAnyHeader()
                .AllowAnyMethod();
        });
});

builder.Services
    .AddAuthentication(options =>
    {
        options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = GoogleDefaults.AuthenticationScheme;
   
      
    })
    .AddCookie()
    .AddGoogle(options =>
    {
        options.ClientId = auth.GoogleClientId;
        options.ClientSecret = auth.GoogleClientSecret;
    });
```

Du kommer att lägga märke till 's CORS-Einträg här, , du behöver också sätta upp dessa i Googles identitetskonsole

![googleidentity.png](googleidentity.png)

Detta gör att Google Auth bara kan användas från domainerna du specifikerar.

## Google Auth In Razor

I min _Layout.cshtml Jag har den här Javascript, det är där jag sätter upp mina Google-knappar och utlöser en tillbakalogg som registrerar ASP

# Google JS

```html
<script src="https://accounts.google.com/gsi/client" async defer></script>
```

Det här är dlow för koden som följer:

```javascript


        
        function renderButton(element)
        {
            google.accounts.id.renderButton(
                element,
                {
                    type: "standard",
                    size: "large",
                    width: 200,
                    theme: "filled_black",
                    text: "sign_in_with",
                    shape: "rectangular",
                    logo_alignment: "left"
                }
            );
        }
        function initGoogleSignIn() {
            google.accounts.id.initialize({
                client_id: "839055275161-u7dqn2oco2729n6i5mk0fe7gap0bmg6g.apps.googleusercontent.com",
                callback: handleCredentialResponse
            });
            const element = document.getElementById('google_button');
            if (element) {
                renderButton(element);
            }
            const secondElement = document.getElementById('google_button2');
            if (secondElement) {
                renderButton(secondElement);
            }
           
        }

        function handleCredentialResponse(response) {
            if (response.credential) {
                const xhr = new XMLHttpRequest();
                xhr.open('POST', '/login', true);
                xhr.setRequestHeader('Content-Type', 'application/json');
                xhr.onload = function () {
                    if (xhr.status === 200) {
                        window.location.reload();
                    } else {
                        console.error('Failed to log in.');
                    }
                };
                xhr.send(JSON.stringify({ idToken: response.credential }));
            } else {
                console.error('No credential in response.');
            }
        }

        window.onload = initGoogleSignIn;

```

Här kan ni se att jag har upp till två div-element i sidan med ID google_knapp och google _knapp

TIP: Om du använder Tailwind , kan du trycka på knappen div för att fungera korrekt i mörk mode ( annars blir det en vit bakgrund kring knappen )

```html
<div class="w-[200px] h-[39px] overflow-hidden rounded">
    <div id="google_button">
    </div>
</div>
```

I JavaScript ovanför skriver jag detta tillbaka till en kontrolleraktion som heter Login. Det här är där jag hanterar Google Auth

```javascript
      const xhr = new XMLHttpRequest();
                xhr.open('POST', '/login', true);
                xhr.setRequestHeader('Content-Type', 'application/json');
                xhr.onload = function () {
                    if (xhr.status === 200) {
                        window.location.reload();
                    } else {
                        console.error('Failed to log in.');
                    }
                };
                xhr.send(JSON.stringify({ idToken: response.credential }));
```

## Google Auth i kontrollören

Controllern är här.

```csharp
    [Route("login")]
        [HttpPost]
        public async Task<IActionResult> HandleGoogleCallback([FromBody] GoogleLoginRequest request)
        {
            var handler = new JwtSecurityTokenHandler();
            var jsonToken = handler.ReadToken(request.IdToken) as JwtSecurityToken;

            if (jsonToken == null)
            {
                return BadRequest("Invalid token");
            }

            var claimsIdentity = new ClaimsIdentity(
                jsonToken.Claims,
                GoogleDefaults.AuthenticationScheme);

            var authProperties = new AuthenticationProperties
            {
                IsPersistent = true
            };

            await HttpContext.SignInAsync(
                CookieAuthenticationDefaults.AuthenticationScheme,
                new ClaimsPrincipal(claimsIdentity),
                authProperties);

            return Ok();
        }
    }
```

NOTA: Det här är inte ', det är prefekten eftersom det uppfattar påståendets namn. (, de är alla småstämde, ), men det fungerar för tillfället

### Controller Base Class för att extrahera inlog properties

I min BaseController extraherar jag de egenskaper som jag behöver;

```csharp
      public record LoginData(bool loggedIn, string? name, string? avatarUrl, string? identifier);
    
    protected LoginData GetUserInfo()
    {
        var authenticateResult = HttpContext.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme).Result;
        if (authenticateResult.Succeeded)
        {
            var principal = authenticateResult.Principal;
            if(principal == null)
            {
                return new LoginData(false, null, null, null);
            }
            var name = principal.FindFirst("name").Value;
            var avatarUrl =principal.FindFirst("picture").Value;
            var nameIdentifier = principal.FindFirst("sub");
            return new LoginData(true, name, avatarUrl, nameIdentifier?.Value);
        }
        return new LoginData(false,null,null,null);
    }
```

Och det är så.