A complete example implementing Google OAuth authentication between an OpenSilver client and ASP.NET Core backend.
- Google account login (Authorization Code Flow)
- JWT token-based API security
- Configuration file-based setup
OpenSilverAuthClient/
├── OpenSilverAuthClient.Browser/ # Client (OpenSilver)
│ ├── wwwroot/
│ │ ├── index.html # Google OAuth config and callback handling
│ │ └── libs/auth.js # Authentication logic
│ └── AuthBar.xaml # Login UI
├── OpenSilverAuthClient.Server/ # Server (ASP.NET Core)
│ ├── Program.cs # Backend authentication logic
│ └── appsettings.json # JWT and Google settings
- User clicks "Login with Google" button
- Redirects to Google login page
- After login completion, returns to site with authorization code
- Client sends code to backend API
- Backend exchanges code for access token with Google
- Backend retrieves user info and generates JWT token
- Client stores JWT token for authenticated API requests
// Google OAuth configuration (change to actual values)
window.AUTH_CFG = {
apiBase: "https://localhost:7224", // Change to actual backend API server address
googleClientId: "YOUR_GOOGLE_CLIENT_ID", // Change to client ID from Google Cloud Console
storageKey: "accessToken"
};
// Authorization code callback handling
const code = new URLSearchParams(window.location.search).get('code');
if (code) {
fetch(window.AUTH_CFG.apiBase + "/auth/google", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ code: code })
})
.then(response => response.json())
.then(data => {
localStorage.setItem(window.AUTH_CFG.storageKey, data.accessToken);
window.location.href = window.location.origin; // Redirect to remove code from URL
});
}
function login() {
const params = new URLSearchParams({
client_id: window.AUTH_CFG.googleClientId,
redirect_uri: window.location.origin, // Automatically uses current page origin
response_type: 'code',
scope: 'email profile openid',
prompt: 'select_account'
});
window.location.href = `https://accounts.google.com/o/oauth2/auth?${params}`;
}
function logout() {
localStorage.removeItem(window.AUTH_CFG.storageKey);
window.location.reload();
}
function getToken() {
return localStorage.getItem(window.AUTH_CFG.storageKey);
}
Complete server-side authentication implementation:
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using System.Text.Json;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
var builder = WebApplication.CreateBuilder(args);
var cfg = builder.Configuration;
// Load configuration values
var jwtKey = cfg["Jwt:Key"]!;
var jwtIssuer = cfg["Jwt:Issuer"]!;
var jwtAudience = cfg["Jwt:Audience"]!;
var jwtHours = cfg.GetValue("Jwt:Hours", 2);
var googleCid = cfg["Google:ClientId"]!;
var googleSecret = cfg["Google:ClientSecret"]!;
var clientOrigin = cfg["Client:Origin"]!;
// Configure CORS
builder.Services.AddCors(p => p.AddDefaultPolicy(b =>
b.WithOrigins(clientOrigin)
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials()));
builder.Services.AddHttpClient();
// Configure JWT Authentication
var signingKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtKey));
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(o => {
o.TokenValidationParameters = new TokenValidationParameters {
ValidateIssuer = true,
ValidIssuer = jwtIssuer,
ValidateAudience = true,
ValidAudience = jwtAudience,
ValidateIssuerSigningKey = true,
IssuerSigningKey = signingKey,
ValidateLifetime = true,
ClockSkew = TimeSpan.FromMinutes(1)
};
});
builder.Services.AddAuthorization();
var app = builder.Build();
app.UseCors();
app.UseAuthentication();
app.UseAuthorization();
// Public endpoint for testing
app.MapGet("/public/ping", () =>
Results.Ok(new { ok = true, msg = "public pong" }))
.AllowAnonymous();
// Google OAuth to JWT token exchange endpoint
app.MapPost("/auth/google", async (GoogleExchange body, IHttpClientFactory httpClientFactory) => {
if (string.IsNullOrWhiteSpace(body.code))
return Results.BadRequest(new { error = "code is required" });
var httpClient = httpClientFactory.CreateClient();
try {
// Step 1: Exchange authorization code for access token
var tokenRequest = new FormUrlEncodedContent(new[] {
new KeyValuePair<string, string>("client_id", googleCid),
new KeyValuePair<string, string>("client_secret", googleSecret),
new KeyValuePair<string, string>("code", body.code),
new KeyValuePair<string, string>("grant_type", "authorization_code"),
new KeyValuePair<string, string>("redirect_uri", clientOrigin)
});
var tokenResponse = await httpClient.PostAsync(
"https://oauth2.googleapis.com/token", tokenRequest);
if (!tokenResponse.IsSuccessStatusCode) {
var errorContent = await tokenResponse.Content.ReadAsStringAsync();
return Results.Problem($"Token exchange failed: {errorContent}", statusCode: 400);
}
var tokenJson = await tokenResponse.Content.ReadAsStringAsync();
var tokenData = JsonSerializer.Deserialize<GoogleTokenResponse>(tokenJson);
// Step 2: Get user information using access token
httpClient.DefaultRequestHeaders.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", tokenData.access_token);
var userResponse = await httpClient.GetAsync(
"https://www.googleapis.com/oauth2/v2/userinfo");
if (!userResponse.IsSuccessStatusCode) {
return Results.Problem("Failed to get user info", statusCode: 400);
}
var userJson = await userResponse.Content.ReadAsStringAsync();
var userData = JsonSerializer.Deserialize<GoogleUserInfo>(userJson);
// Step 3: Create claims from user data
var claims = new[] {
new Claim(ClaimTypes.NameIdentifier, userData.id ?? ""),
new Claim(ClaimTypes.Name, userData.name ?? userData.email ?? ""),
new Claim(ClaimTypes.Email, userData.email ?? ""),
new Claim("picture", userData.picture ?? "")
};
// Step 4: Generate JWT token
var token = new JwtSecurityToken(
issuer: jwtIssuer,
audience: jwtAudience,
claims: claims,
expires: DateTime.UtcNow.AddHours(jwtHours),
signingCredentials: new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256)
);
return Results.Ok(new {
accessToken = new JwtSecurityTokenHandler().WriteToken(token)
});
}
catch (Exception ex) {
return Results.Problem(ex.Message, statusCode: StatusCodes.Status500InternalServerError);
}
}).AllowAnonymous();
// Protected endpoint example
app.MapGet("/secure/ping", (ClaimsPrincipal user) => {
var name = user.Identity?.Name ?? "";
var email = user.FindFirst(ClaimTypes.Email)?.Value ?? "";
return Results.Ok(new { ok = true, msg = "secure pong", name, email });
}).RequireAuthorization();
app.Run();
// Record types for request/response
record GoogleExchange(string code);
record GoogleTokenResponse(string access_token, string token_type, int expires_in);
record GoogleUserInfo(string id, string email, string name, string picture);
{
"Jwt": {
"Key": "minimum-32-character-secret-key-required-here-change-this",
"Issuer": "OpenSilver.AuthService",
"Audience": "OpenSilver.Clients",
"Hours": 2
},
"Google": {
"ClientId": "YOUR_GOOGLE_CLIENT_ID",
"ClientSecret": "YOUR_GOOGLE_CLIENT_SECRET"
},
"Client": {
"Origin": "http://localhost:55592"
}
}
- Go to Google Cloud Console
- Create a new project or select existing project
- Enable Google+ API
- Go to Credentials → Create Credentials → OAuth Client ID
- Select Application type: "Web application"
- Add your client address to Authorized redirect URIs (e.g.,
http://localhost:55592
) - Copy generated Client ID and Client Secret
- Apply Google Client ID and Secret to both
index.html
andappsettings.json
- Ensure JWT Key is at least 32 characters long
- Set correct backend API address in
index.html
(window.AUTH_CFG.apiBase
) - Set correct client origin in
appsettings.json
(Client:Origin
) - Verify redirect URI in Google Console matches your client address exactly
"redirect_uri_mismatch" error
- Ensure the redirect URI in Google Console exactly matches your client origin
- Check for trailing slashes or http vs https mismatches
CORS error
- Verify
Client:Origin
inappsettings.json
matches your actual client address - Ensure the server is running and accessible
JWT key length error
- JWT Key must be at least 32 characters (256 bits)
- Generate a secure random key for production use
Token exchange failed
- Verify Google Client ID and Secret are correctly configured in both client and server
- Check that the authorization code hasn't expired (they're single-use and short-lived)
- Start the backend server (should run on configured port, e.g., https://localhost:7224)
- Start the OpenSilver client (should run on configured port, e.g., http://localhost:55592)
- Click "Login with Google" button
- Complete Google authentication
- Verify you're redirected back and JWT token is stored
- Test protected endpoints with the stored token
- Never commit secrets: Keep
appsettings.json
with real credentials out of source control - Use HTTPS in production: JWT tokens should always be transmitted over HTTPS
- Secure token storage: Consider using httpOnly cookies instead of localStorage for better security
- Token expiration: Implement token refresh mechanism for better user experience
- Environment-specific configs: Use separate configurations for development and production