-
Notifications
You must be signed in to change notification settings - Fork 0
/
KeycloakOpenIdConnectAuth.cs
135 lines (116 loc) · 4.74 KB
/
KeycloakOpenIdConnectAuth.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
using System;
using System.Configuration;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using System.Web;
using IdentityModel;
using IdentityModel.Client;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.Owin;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Cookies;
using Microsoft.Owin.Security.Notifications;
using Microsoft.Owin.Security.OpenIdConnect;
using Owin;
[assembly: OwinStartup( typeof( Web.Startup.Startup ) )]
namespace Web.Startup
{
public class Startup
{
private readonly string _keyCloakAuthUrl = ConfigurationManager.AppSettings["KeycloakAuthUrl"];
private readonly string _clientId = ConfigurationManager.AppSettings["KeycloakClientId"];
private readonly string _clientSecret = ConfigurationManager.AppSettings["KeycloakClientSecret"];
// you can use CryptoRandom.CreateUniqueId( 32 ) to create "_codeVerifier" text
private readonly string _codeVerifier = ConfigurationManager.AppSettings["KeycloakCodeVerifier"];
public void Configuration( IAppBuilder app )
{
app.UseCookieAuthentication( new CookieAuthenticationOptions {
AuthenticationType = OpenIdConnectAuthenticationDefaults.AuthenticationType
} );
app.UseOpenIdConnectAuthentication( new OpenIdConnectAuthenticationOptions {
ClientId = _clientId,
ClientSecret = _clientSecret,
Authority = _keyCloakAuthUrl,
ResponseType = OpenIdConnectResponseType.Code,
Scope = OpenIdConnectScope.OpenIdProfile,
UsePkce = true,
SignInAsAuthenticationType = OpenIdConnectAuthenticationDefaults.AuthenticationType,
Notifications = new OpenIdConnectAuthenticationNotifications {
AuthorizationCodeReceived = ValidateTokenAndSetClaims,
AuthenticationFailed = OnAuthenticationFailed,
RedirectToIdentityProvider = SetRequestParameters,
},
} );
}
private Task SetRequestParameters( RedirectToIdentityProviderNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> context )
{
if (context.ProtocolMessage.RequestType == OpenIdConnectRequestType.Authentication) {
// this is required for PKCE
string codeChallenge;
using (var sha256 = SHA256.Create()) {
var challengeBytes = sha256.ComputeHash( Encoding.UTF8.GetBytes( _codeVerifier ) );
codeChallenge = Base64Url.Encode( challengeBytes );
}
// set code_challenge parameter on authorization request
context.ProtocolMessage.SetParameter( "code_challenge", codeChallenge );
context.ProtocolMessage.SetParameter( "code_challenge_method", "S256" );
// if you want to bypass the keycloak login page
context.ProtocolMessage.SetParameter( "kc_idp_hint", "YOUR_IDENTITY_PROVIDER_ALIAS" );
}
// this will set dynamic redirect uri
context.ProtocolMessage.RedirectUri = context.Request.Uri.AbsoluteUri;
return Task.CompletedTask;
}
private Task ValidateTokenAndSetClaims( AuthorizationCodeReceivedNotification context )
{
// this is to handle when user refreshes the browser immediate after a successful authentication
if (((ClaimsPrincipal)HttpContext.Current.User).Identities.First().NameClaimType == "preferred_username") {
return Task.CompletedTask;
}
var client = new HttpClient();
var codeTokenAsync = client.RequestAuthorizationCodeTokenAsync( new AuthorizationCodeTokenRequest {
Address = $"{_keyCloakAuthUrl}/protocol/openid-connect/token",
ClientId = _clientId,
ClientSecret = _clientSecret,
Code = context.Code,
RedirectUri = context.Request.Uri.AbsoluteUri,
CodeVerifier = _codeVerifier
} );
var tokenResponse = codeTokenAsync.GetAwaiter().GetResult();
if (tokenResponse.IsError) {
throw new Exception( tokenResponse.Raw );
}
var userInfoAsync = client.GetUserInfoAsync( new UserInfoRequest {
Address = $"{_keyCloakAuthUrl}/protocol/openid-connect/userinfo",
Token = tokenResponse.AccessToken
} );
var userInfo = userInfoAsync.GetAwaiter().GetResult();
if (userInfo.IsError) {
throw new Exception( tokenResponse.Raw );
}
var claims = userInfo.Claims;
context.AuthenticationTicket = new AuthenticationTicket(
new ClaimsIdentity(
HttpContext.Current.User.Identity,
claims,
OpenIdConnectAuthenticationDefaults.AuthenticationType,
"preferred_username",
((ClaimsPrincipal)HttpContext.Current.User).Identities.First().RoleClaimType
),
new AuthenticationProperties()
);
return Task.CompletedTask;
}
private Task OnAuthenticationFailed( AuthenticationFailedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> context )
{
context.HandleResponse();
context.Response.Write( "Something went wrong." );
return Task.CompletedTask;
}
}
}