-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add authentication cookie model and configure data protection settings
- Loading branch information
Showing
11 changed files
with
278 additions
and
232 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
ConnectionStrings__MongoDb= "mongodb://USERNAME:PASSWORD@mongo:27017" | ||
Jwt__PrivateKey= "PRIVATE_KEY" | ||
Jwt__Issuer= "https://localhost:8001" | ||
Jwt__AccessTokenExpirationMinutes= 2 | ||
Jwt__Audience= "http://localhost:5010" | ||
Jwt__DataProtectionApplicationName= "microidp" | ||
Jwt__DataProtectionKeysPath= "./DataProtectionKeys" | ||
Jwt__CookieName= "microidp" | ||
Jwt__DataProtectionPurpose= "JwtCookieEncryption" | ||
OAuth__GoogleCallbackURL= "https://localhost:8001/api/auth/google-callback" | ||
OAuth__GoogleClientId= "GOOGLE_CLIENT_ID" | ||
OAuth__GoogleClientSecret= "GOOGLE_CLIENT_SECRET" | ||
ASPNETCORE_URLS= "https://+;http://+" | ||
ASPNETCORE_Kestrel__Certificates__Default__Path= "/https/aspnetcore.pfx" | ||
ASPNETCORE_Kestrel__Certificates__Default__Password= "1234567890" | ||
Cors__Origins= "http://localhost:3000" | ||
DBName= "microIDP" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -47,3 +47,4 @@ aspnetcore.crt | |
aspnetcore.key | ||
aspnetcore.pfx | ||
**/DataProtectionKeys/ | ||
.env |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
namespace Domain.Models; | ||
|
||
public class AuthCookie | ||
{ | ||
public string AccessToken { get; set; } | ||
public string RefreshToken { get; set; } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,140 +1,161 @@ | ||
# Micro IDP Service | ||
|
||
## Features: | ||
## Features | ||
|
||
- Sign up with Email | ||
- Sign in with Email | ||
- Sign in with Google | ||
- Generate JWT | ||
- Generate Refresh Token | ||
- Sign up/in with Email | ||
- Sign up/in with Google | ||
|
||
## Usage | ||
## Run in Docker | ||
|
||
- #### Generate Private and Public key | ||
### Generate certificate to host application with Docker over HTTPS | ||
|
||
- C# Interactive | ||
#### Windows | ||
|
||
```C# | ||
using System.Security.Cryptography; | ||
using (var rsa = RSA.Create(2048)) | ||
{ | ||
// Export the private key | ||
var privateKey = rsa.ExportRSAPrivateKey(); | ||
var privateKeyBase64 = Convert.ToBase64String(privateKey); | ||
Console.WriteLine("Private Key:"); | ||
Console.WriteLine(privateKeyBase64); | ||
```shell | ||
dotnet dev-certs https -ep %USERPROFILE%\.aspnet\https\aspnetapp.pfx -p <CREDENTIAL_PLACEHOLDER> | ||
dotnet dev-certs https --trust | ||
``` | ||
|
||
// Export the public key | ||
var publicKey = rsa.ExportRSAPublicKey(); | ||
var publicKeyBase64 = Convert.ToBase64String(publicKey); | ||
Console.WriteLine("\nPublic Key:"); | ||
Console.WriteLine(publicKeyBase64); | ||
} | ||
``` | ||
In the preceding commands, replace `<CREDENTIAL_PLACEHOLDER>` with a password. | ||
|
||
- BASH | ||
#### Linux | ||
|
||
```SHELL | ||
openssl genpkey -algorithm RSA -out private_key.pem -pkeyopt rsa_keygen_bits:2048 | ||
cat private_key.pem | base64 | ||
```shell | ||
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout aspnetcore.key -out aspnetcore.crt -subj "/CN=localhost" | ||
openssl pkcs12 -export -out aspnetcore.pfx -inkey aspnetcore.key -in aspnetcore.crt | ||
``` | ||
|
||
openssl rsa -pubout -in private_key.pem -out public_key.pem | ||
cat public_key.pem | base64 | ||
``` | ||
Replace **_ASPNETCORE_Kestrel\_\_Certificates\_\_Default\_\_Password_** with the certificate password in `.env`. | ||
|
||
- #### Replace PRIVATE_KEY placeholder in docker-compose.yml with generated private key | ||
Replace the volume mount source with the generated certificate path in `docker-compose.yml`: | ||
|
||
```YML | ||
webapi: | ||
build: . | ||
ports: | ||
- 8000:80 | ||
- 8001:443 | ||
environment: | ||
JWT__PrivateKey: "PRIVATE_KEY" | ||
``` | ||
```yml | ||
volumes: | ||
- type: bind | ||
source: ./aspnetcore.pfx | ||
target: /https/aspnetcore.pfx | ||
``` | ||
- #### Generate certificate to host application with docker over HTTPS | ||
To share data protection keys for encrypting and decrypting cookies, create an empty folder and bind it into the Docker container: | ||
- windows | ||
```yml | ||
volumes: | ||
- type: bind | ||
source: ./DataProtectionKeys | ||
target: /app/DataProtectionKeys | ||
``` | ||
```SHELL | ||
dotnet dev-certs https -ep %USERPROFILE%\.aspnet\https\aspnetapp.pfx -p <CREDENTIAL_PLACEHOLDER> | ||
dotnet dev-certs https --trust | ||
``` | ||
### Generate Private and Public Key | ||
In the preceding commands, replace <CREDENTIAL_PLACEHOLDER> with a password. | ||
#### C# Interactive | ||
- Linux | ||
```csharp | ||
using System.Security.Cryptography; | ||
using (var rsa = RSA.Create(2048)) | ||
{ | ||
// Export the private key | ||
var privateKey = rsa.ExportRSAPrivateKey(); | ||
var privateKeyBase64 = Convert.ToBase64String(privateKey); | ||
Console.WriteLine("Private Key:"); | ||
Console.WriteLine(privateKeyBase64); | ||
|
||
```SHELL | ||
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout aspnetcore.key -out aspnetcore.crt -subj "/CN=localhost" | ||
``` | ||
// Export the public key | ||
var publicKey = rsa.ExportRSAPublicKey(); | ||
var publicKeyBase64 = Convert.ToBase64String(publicKey); | ||
Console.WriteLine("\nPublic Key:"); | ||
Console.WriteLine(publicKeyBase64); | ||
} | ||
``` | ||
|
||
```SHELL | ||
openssl pkcs12 -export -out aspnetcore.pfx -inkey aspnetcore.key -in aspnetcore.crt | ||
``` | ||
#### Bash | ||
|
||
Replace **_ASPNETCORE_Kestrel\_\_Certificates\_\_Default\_\_Password_** with certificate password in docker-compose.yml | ||
```shell | ||
openssl genpkey -algorithm RSA -out private_key.pem -pkeyopt rsa_keygen_bits:2048 | ||
cat private_key.pem | base64 | ||
|
||
Replace volume mount source with generated certificate path in docker-compose.yml | ||
openssl rsa -pubout -in private_key.pem -out public_key.pem | ||
cat public_key.pem | base64 | ||
``` | ||
|
||
```YML | ||
volumes: | ||
- type: bind | ||
source: ./aspnetcore.pfx | ||
target: /https/aspnetcore.pfx | ||
``` | ||
|
||
- #### Sing in with Google configuration | ||
|
||
- Create OAuth2.0 client in [Google Cloud Console](https://console.cloud.google.com). | ||
- Replace **_OAuth_GoogleClientId_** placeholder in docker-compose.yml | ||
- Replace **_OAuth_GoogleClientSecret_** placeholder in docker-compose.yml | ||
- Replace **_OAuth_GoogleCallBackURL_** placeholder in docker-compose.yml with your client app google callback page (this page should call https://IDP_SERVER_URL/api/auth/google-callback to get JWT) | ||
|
||
- #### Run IDP | ||
|
||
```SHELL | ||
docker compose up --wait | ||
``` | ||
|
||
### Client App | ||
|
||
- #### Add Jwt section to you appsettings.json | ||
|
||
```JSON | ||
"Jwt": { | ||
"PublicKey": "PUBLIC_KEY", | ||
"Issuer": "https://localhost:8001", | ||
"Audience": "http://localhost:5010" | ||
} | ||
``` | ||
|
||
Replace PUBLIC_KEY placeholder with generated public key | ||
|
||
- #### Install JwtBearer package | ||
```SHELL | ||
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer | ||
``` | ||
- #### Add Authentication middleware | ||
|
||
```C# | ||
var rsa = RSA.Create(); | ||
rsa.ImportRSAPublicKey(Convert.FromBase64String(configuration["Jwt:PublicKey"] ?? ""), out _); | ||
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) | ||
.AddJwtBearer(options => | ||
{ | ||
options.TokenValidationParameters = new TokenValidationParameters | ||
{ | ||
ValidateIssuer = true, | ||
ValidateAudience = true, | ||
ValidateLifetime = true, | ||
ValidateIssuerSigningKey = true, | ||
ValidIssuer = configuration["Jwt:Issuer"], | ||
ValidAudience = configuration["Jwt:Audience"], | ||
IssuerSigningKey = new RsaSecurityKey(rsa) | ||
}; | ||
}); | ||
``` | ||
Replace the `PRIVATE_KEY` placeholder in `.env` with the generated private key. | ||
|
||
### Sign in with Google Configuration | ||
|
||
1. Create an OAuth 2.0 client in [Google Cloud Console](https://console.cloud.google.com). | ||
2. Replace **_OAuth\_\_GoogleClientId_** placeholder in `.env`. | ||
3. Replace **_OAuth\_\_GoogleClientSecret_** placeholder in `.env`. | ||
4. Replace **_OAuth\_\_GoogleCallBackURL_** placeholder in `.env` with your client app's Google callback page (this page should call `https://IDP_SERVER_URL/api/auth/google-callback` to get JWT). | ||
|
||
### Run IDP | ||
|
||
```shell | ||
docker compose up --wait | ||
``` | ||
|
||
## Client App | ||
|
||
### Add Jwt Section to Your `appsettings.json` | ||
|
||
```json | ||
"Jwt": { | ||
"PublicKey": "PUBLIC_KEY", | ||
"Issuer": "https://localhost:8001", | ||
"Audience": "http://localhost:5010", | ||
"CookieName": "SAME AS IDP .env Jwt__CookieName", | ||
"DataProtectionPurpose": "SAME AS IDP .env Jwt__DataProtectionPurpose" | ||
} | ||
``` | ||
|
||
Replace the `PUBLIC_KEY` placeholder with the generated public key. | ||
|
||
### Install JwtBearer Package | ||
|
||
```shell | ||
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer | ||
``` | ||
|
||
### Add Authentication Middleware | ||
|
||
```csharp | ||
var rsa = RSA.Create(); | ||
rsa.ImportRSAPublicKey(Convert.FromBase64String(configuration["Jwt:PublicKey"] ?? ""), out _); | ||
|
||
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) | ||
.AddJwtBearer(options => | ||
{ | ||
options.TokenValidationParameters = new TokenValidationParameters | ||
{ | ||
ValidateIssuer = true, | ||
ValidateAudience = true, | ||
ValidateLifetime = true, | ||
ValidateIssuerSigningKey = true, | ||
ValidIssuer = configuration["Jwt:Issuer"], | ||
ValidAudience = configuration["Jwt:Audience"], | ||
IssuerSigningKey = new RsaSecurityKey(rsa) | ||
}; | ||
options.Events = new JwtBearerEvents | ||
{ | ||
OnMessageReceived = context => | ||
{ | ||
if (context.Request.Cookies.TryGetValue(configuration["Jwt:CookieName"], out var encryptedToken)) | ||
{ | ||
var dataProtector = context.HttpContext.RequestServices | ||
.GetRequiredService<IDataProtectionProvider>() | ||
.CreateProtector(configuration["Jwt:DataProtectionPurpose"]); | ||
|
||
try | ||
{ | ||
var authCookie = JsonSerializer.Deserialize<AuthCookie>(dataProtector.Unprotect(encryptedToken)); | ||
context.Token = authCookie.AccessToken; | ||
} | ||
catch | ||
{ | ||
context.Fail("Invalid or tampered token"); | ||
} | ||
} | ||
|
||
return Task.CompletedTask; | ||
} | ||
}; | ||
}); | ||
``` |
Oops, something went wrong.