We have an api (.net core 2.2) which use IdentityServerAuthenticationDefaults.AuthenticationScheme for all the controllers which works fine.
We now decide to add SignalR Hub for a conference service.
The hub is working fine only if we remove the authorize attribute [Authorize(AuthenticationSchemes = IdentityServerAuthenticationDefaults.AuthenticationScheme)]
We did try to handle the token in the query using the following both methods (TokenRetriever or JwrBearerEvents) :
services.AddAuthentication()
.AddIdentityServerAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme, options =>
{
options.Authority = AuthURL;
options.SupportedTokens = SupportedTokens.Jwt;
options.RequireHttpsMetadata = HttpsSetting;
options.ApiName = APIs.API_Commerce;
options.TokenRetriever = new Func<HttpRequest, string>(req =>
{
var fromHeader = TokenRetrieval.FromAuthorizationHeader();
var fromQuery = TokenRetrieval.FromQueryString();
return fromHeader(req) ?? fromQuery(req);
});
options.JwtBearerEvents.OnMessageReceived = context =>
{
var accessToken = context.Request.Query["access_token"];
// If the request is for our hub...
var path = context.HttpContext.Request.Path;
if (!string.IsNullOrEmpty(accessToken) &&
(path.StartsWithSegments("/hubs/")))
{
// Read the token out of the query string
context.Token = accessToken;
}
return Task.CompletedTask;
};
});
For some reason theses only fire when we call controllers but ignore all invoked methods from the client.
Note that we have an AuthServer which provide the tokens and an API.
We are using angular 7 with aspnet/signalr module for the client side.
I found the problem...
app.UseAuthentication() was added in Configure
Add default scheme to authentication and remove onmessagereceive ->
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = IdentityServerAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = IdentityServerAuthenticationDefaults.AuthenticationScheme;
})
.AddIdentityServerAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme, options =>
{
options.Authority = AuthURL;
options.SupportedTokens = SupportedTokens.Jwt;
options.RequireHttpsMetadata = HttpsSetting;
options.ApiName = APIs.API_Commerce;
options.TokenRetriever = new Func<HttpRequest, string>(req =>
{
var fromHeader = TokenRetrieval.FromAuthorizationHeader();
var fromQuery = TokenRetrieval.FromQueryString();
return fromHeader(req) ?? fromQuery(req);
});
});
Just to mention with .net core 2.2 u must specified an origin (withOrigins) and cannot use Any..
Related
I am using OpenID to get ID_token/ accesstoken. The problem is in OpenID workflow I am able to get authorization code from identity server but next step is to use the authorisation code and call the oauth/token endpoint to get the access token.
In order to call the Oauth/token endpoint , I need to pass the client ID and client secret as Request header(basic authentication) but it is getting passed in the request body, how do I get to pass it in the header instead of request body??
Here is the sample code from Startup.cs (.Net core 3.1)
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie(options =>
{
options.Events = new CookieAuthenticationEvents()
{
OnSigningIn = async context =>
{
var scheme = context.Properties.Items.Where(k => k.Key == ".AuthScheme").FirstOrDefault();
var claim = new Claim(scheme.Key, scheme.Value);
var claimsIdentity = context.Principal.Identity as ClaimsIdentity;
claimsIdentity.AddClaim(claim);
await Task.CompletedTask;
}
};
})
.AddOpenIdConnect("test",o => {
o.SignInScheme = "Cookies";
o.ClientId = "id";
o.ClientSecret = "08";
o.Authority = "https://ex.com";
o.ResponseType = "code";
o.MetadataAddress = "https://ex.com/.well-known/openid-configuration";
o.SaveTokens = true;
o.GetClaimsFromUserInfoEndpoint = true;
});
services.AddAuthorization(options =>
{
options.AddPolicy("BasicAuthentication", new AuthorizationPolicyBuilder("BasicAuthentication").RequireAuthenticatedUser().Build());
});
services.AddControllersWithViews();
services.AddRazorPages();
}
You don't need to try to get the ID/Access-token manually, it is all handled by the AddOpenIdConnect middleware. When the request comes back with the authorization code, then AddOpenIdConnect will automatically get the tokens for you.
In your code I would change to the following because the typical case is that you want to have the OpenIDConnect handler to handle the challenge part.
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
I have an ASP.NET MVC application (.Net Framework 4.7.2) using openidconnect authentication with okta configured. When the user is not authorised, the app redirects the user to okta for login, and it works fine for web browser.
We have a requirement to allow mobile app to render certain pages within app via web view and they will be passing request header authorization with access token.
After a bit Google search, I found that I can add jwt and openidconnect authentication so will check request header for authorization header if exist we will use jwt else openidconnect.
I tried with .NET Core 2.2 and it works fine, but I'm not sure how can I implement something similar in .net framework.
.NET Core code snippet
services.AddAuthentication("DefaultPolicy")
.AddJwtBearer(options => {
options.Authority = Configuration["Okta:Issuer"];
options.Audience = "auth";
})
.AddCookie()
.AddOpenIdConnect(options => {
options.ClientId = Configuration["Okta:ClientId"];
options.ClientSecret = Configuration["Okta:ClientSecret"];
options.Authority = Configuration["Okta:Issuer"];
options.CallbackPath = "/authorization-code/callback";
options.ResponseType = "code";
options.SaveTokens = true;
options.UseTokenLifetime = false;
options.GetClaimsFromUserInfoEndpoint = true;
options.Scope.Add("openid");
options.Scope.Add("profile");
options.TokenValidationParameters = new TokenValidationParameters {
NameClaimType = "name"
};
})
.AddPolicyScheme("DefaultPolicy", "Authorization Bearer or OIDC", o => {
o.ForwardAuthenticate = "AuthenticateSignInPolicy";
o.ForwardSignIn = "AuthenticateSignInPolicy";
o.ForwardChallenge = "ChallengePolicy";
})
.AddPolicyScheme("AuthenticateSignInPolicy", "Authorization Bearer or OIDC", options => {
options.ForwardDefaultSelector = context => {
var authHeader = context.Request.Headers["Authorization"].FirstOrDefault();
if (authHeader?.StartsWith("Bearer ") == true)
{
return JwtBearerDefaults.AuthenticationScheme;
}
return CookieAuthenticationDefaults.AuthenticationScheme;
};
})
.AddPolicyScheme("ChallengePolicy", "Authorization Bearer or OIDC", options => {
options.ForwardDefaultSelector = context => {
var authHeader = context.Request.Headers["Authorization"].FirstOrDefault();
if (authHeader?.StartsWith("Bearer ") == true)
{
return JwtBearerDefaults.AuthenticationScheme;
}
return OpenIdConnectDefaults.AuthenticationScheme;
};
});
I will suppose here, that you are following the quick start provided by OKTA:
https://developer.okta.com/quickstart/#/okta-sign-in-page/dotnet/aspnet4.
Inside their guide, they tell you to add a Startup class. You need to replace their "app.UseOktaMVC" by "app.AddJwtBearerAuthentication".
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.AddJwtBearerAuthentication(new OktaWebApiOptions()
{
OktaDomain = Constants.GetIssuer,
AuthorizationServerId = string.Empty,
Audience = Constants.GetAudience,
});
}
}
The extension is provided by OKTA. If you want to see how to register it all by yourself, their source code is available on github.
https://github.com/okta/okta-aspnet/blob/master/Okta.AspNet/OktaMiddlewareExtensions.cs
I have a simple application which uses Angular as front-end and a .NET Core Web API as back-end services. Now I want to secure my WEB API layer. I though I can use OpenID Connect for that purpose. But all the examples or documentation online uses some identity management systems to like (Keycloak, Okta) but i just want to use my user data from a SQL database.
So something like, I hit the WEB API from Angular to get the token generated(using OpenID?) based on the user details sent. The i can just use the token to Authorize users. I want to use OpenID so that i can use some other identity management systems later if i want to.
my startup class in WEB API
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect(o =>
{
o.ClientId = "sa";
o.ClientSecret = "sa";
o.Authority = "https://localhost:44352";
o.GetClaimsFromUserInfoEndpoint = true;
o.RequireHttpsMetadata = true;
});
I added a controller with Authorize attribute and added a test method to see what happens when i hit that from Swagger
I see the following error
IOException: IDX20804: Unable to retrieve document from: 'https://localhost:44352/.well-known/openid-configuration'
I am not sure what the error is.
Also I would like to ask if i doing this correct. Can i use the same API (Authority? as in ( o.Authority = "https://localhost:44352";)) to authenticate/get token from).
What you'll need for OpenIDConnect is definitely a Server that implements the oidc-spec.
The .well-known/... url is part of the oidc discovery spec, which Servers like identityserver and keycloak implement. It gives a standardized List of other endpoints to retrieve tokens, get userinfo, get logouturi etc.
Your API does not have such an endpoint, so that's what the error says.
If you want to implement the whole oidc-spec on your own, go for it, but I wouldn't recommend it, it's kind of complex. As I see it, .net core does only implement an openidconnect-client and you're free to choose the server implementation as long as it implements the spec.
Alternatively, have a look at JwtBearer, which is a more lightweight approach to authentication and more easy to implement yourself (and, best of: you could easily change to oidc later on). Good starting points might be these blogposts.
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
}).AddCookie(CookieAuthenticationDefaults.AuthenticationScheme,
options =>
{
options.LoginPath = new PathString("/");
options.AccessDeniedPath = "/Identity/Account/AccessDenied";
options.Cookie = new CookieBuilder()
{
SecurePolicy = CookieSecurePolicy.SameAsRequest,
Path = "/"
};
options.SlidingExpiration = true;
options.ExpireTimeSpan = TimeSpan.FromMinutes(sessionTimeout);
}).AddOpenIdConnect(options =>
{
options.ClientId = Configuration.GetValue<string>("oidc:ClientId");
options.ClientSecret = Configuration["oidc:ClientSecret"];
options.CallbackPath = new PathString("/auth/callback");
options.GetClaimsFromUserInfoEndpoint = true;
options.Authority = Configuration["oidc:Authority"];
options.SignedOutRedirectUri = "/";
options.RequireHttpsMetadata = false;
options.SaveTokens = true;
options.UseTokenLifetime = true;
var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(Configuration["oidc:ClientSecret"]));
options.TokenValidationParameters = new TokenValidationParameters
{
RequireSignedTokens = true,
IssuerSigningKey = signingKey,
ValidateAudience = true,
ValidateIssuer = true,
ValidateIssuerSigningKey = true,
};
options.ResponseType = OpenIdConnectResponseType.Code;
options.AuthenticationMethod = OpenIdConnectRedirectBehavior.RedirectGet;
options.Scope.Clear();
options.Scope.Add("openid");
options.Scope.Add("email");
options.Scope.Add("profile");
options.Events = new OpenIdConnectEvents()
{
OnTicketReceived = context =>
{
var identity = context.Principal.Identity as ClaimsIdentity;
if (identity != null)
{
if (!context.Principal.HasClaim(c => c.Type == ClaimTypes.Name) &&
identity.HasClaim(c => c.Type == "name"))
identity.AddClaim(new Claim(ClaimTypes.Name, identity.FindFirst("name").Value));
if (context.Properties.Items.ContainsKey(".TokenNames"))
{
string[] tokenNames = context.Properties.Items[".TokenNames"].Split(';');
foreach (var tokenName in tokenNames)
{
string tokenValue = context.Properties.Items[$".Token.{tokenName}"];
identity.AddClaim(new Claim(tokenName, tokenValue));
}
}
}
var cp = new ClaimsPrincipal(identity);
context.Principal = cp;
return Task.CompletedTask;
},
//OnTokenValidated = context =>
//{
// ClaimsIdentity identity = (ClaimsIdentity)context.Principal.Identity;
// Claim Name = identity.FindFirst("preferred_username");
// Claim gender = identity.FindFirst(ClaimTypes.Gender);
// Claim sub = identity.FindFirst(ClaimTypes.NameIdentifier);
// var claimsToKeep = new List<Claim> { Name, gender, sub };
// var newIdentity = new ClaimsIdentity(claimsToKeep, identity.AuthenticationType);
// context.Principal = new ClaimsPrincipal(newIdentity);
// return Task.FromResult(0);
//},
OnAuthenticationFailed = context =>
{
context.Response.Redirect("/");
context.HandleResponse();
return Task.CompletedTask;
},
OnRedirectToIdentityProvider = context =>
{
context.ProtocolMessage.SetParameter("pfidpadapterid", Configuration["oidc:pfidpadapterid"]);
return Task.FromResult(0);
}
};
});
I have following in my client startup.cs.
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect(options =>
{
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; // cookie middle setup above
options.Authority = AuthSetting["Authority"]; // Auth Server
options.RequireHttpsMetadata = false; // only for development
options.ClientId = AuthSetting["ClientId"]; // client setup in Auth Server
options.ClientSecret = AuthSetting["ClientSecret"];
options.ResponseType = "code id_token"; // means Hybrid flow (id + access token)
options.GetClaimsFromUserInfoEndpoint = true;
options.SaveTokens = true;
//options.ClaimActions.MapJsonKey(ClaimTypes.Email, "email", ClaimValueTypes.Email);
//options.ClaimActions.Clear(); //https://stackoverflow.com/a/47896180/9263418
//options.ClaimActions.MapUniqueJsonKey("Aes", "Aes");
//options.ClaimActions.MapUniqueJsonKey("foo", "foo");
//options.ClaimActions.MapJsonKey("Aes", "Aes"); //https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers/issues/210
});
Following is my Identityserver's startup.cs
services.AddIdentityServer(options =>
{
options.Events.RaiseSuccessEvents = true;
options.Events.RaiseFailureEvents = true;
options.Events.RaiseErrorEvents = true;
options.Events.RaiseInformationEvents = true;
})
.AddInMemoryClients(Clients.Get())
.AddInMemoryIdentityResources(Resources.GetIdentityResources())
.AddInMemoryApiResources(Resources.GetApiResources())
.AddDeveloperSigningCredential()
.AddExtensionGrantValidator<Extensions.ExtensionGrantValidator>()
.AddExtensionGrantValidator<Extensions.NoSubjectExtensionGrantValidator>()
.AddJwtBearerClientAuthentication()
.AddAppAuthRedirectUriValidator()
.AddClientConfigurationValidator<DefaultClientConfigurationValidator>()
.AddProfileService<ProfileService>();
Following is my ProfileService.cs file.
public class ProfileService : IProfileService
{
public Task GetProfileDataAsync(ProfileDataRequestContext context)
{
// Processing
var claims = new List<Claim>
{
new Claim("Email", "someone2gmail.com"),
};
context.IssuedClaims.AddRange(claims);
return Task.FromResult(0);
}
public Task IsActiveAsync(IsActiveContext context)
{
// Processing
context.IsActive = true;
return Task.FromResult(0);
}
}
I am not able to access Mail claim in client application.
Checked many references.
But none of them are working for me. Any guess that what might be missing?
Using Identityserver4 with .Net core 2.
Never mind. I got it resolved by trying following option in client configuration of server. Will read it entirely. But for now it works as it seems to be including claims in token.
AlwaysIncludeUserClaimsInIdToken = true
The default scopes for OpenIDConnectOptions are "openid" and "profile".
You will have to additionally request the "email" scope when configuring your options.
I'm using mixed authentication in my ASP.NET Core 2.0 Web and API app. Meaning both cookies and now adding JWT token.
The web part of the app uses cookies and in the API part, I want to use JWT token.
My question is how do I get the claims from JWT token? In my web controllers, I can simply use HttpContext.User; to get the claims stored in a cookie. How do I handle it in my API methods where I want to use JWT token?
Here's my AuthenticationBuilder:
public static void MyAuthenticationConfig(IServiceCollection services, IConfiguration configuration)
{
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = "myApp_cookie";
options.DefaultChallengeScheme = "myApp_cookie";
})
.AddCookie("myApp_cookie", options =>
{
options.AccessDeniedPath = "/Unauthorized";
options.LoginPath = "/Login";
})
.AddCookie("social_auth_cookie")
.AddOAuth("LinkedIn", options =>
{
options.SignInScheme = "social_auth_cookie";
options.ClientId = "my_client_id";
options.ClientSecret = "my_secret";
options.CallbackPath = "/linkedin-callback";
options.AuthorizationEndpoint = "https://www.linkedin.com/oauth/v2/authorization";
options.TokenEndpoint = "https://www.linkedin.com/oauth/v2/accessToken";
options.UserInformationEndpoint = "https://api.linkedin.com/v1/people/~:(id,first-name,last-name,email-address,picture-url,picture-urls::(original))";
options.Scope.Add("r_basicprofile");
options.Scope.Add("r_emailaddress");
options.Events = new OAuthEvents
{
OnCreatingTicket = OnCreatingTicketLinkedInCallBack,
OnTicketReceived = OnTicketReceivedCallback
};
})
.AddFacebook(options =>
{
options.SignInScheme = "social_auth_cookie";
options.AppId = "my_app_is";
options.AppSecret = "my_secret";
options.Events = new OAuthEvents
{
OnCreatingTicket = OnCreatingTicketFacebookCallback,
OnTicketReceived = OnTicketReceivedCallback
};
})
.AddGoogle(options =>
{
options.SignInScheme = "social_auth_cookie";
options.ClientId = "my_id.apps.googleusercontent.com";
options.ClientSecret = "my_secret";
options.CallbackPath = "/google-callback";
options.Events = new OAuthEvents
{
OnCreatingTicket = OnCreatingTicketGoogleCallback,
OnTicketReceived = OnTicketReceivedCallback
};
})
.AddJwtBearer("JwtBearer", jwtBearerOptions =>
{
jwtBearerOptions.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("my_secret")),
ValidateIssuer = true,
ValidIssuer = "my-api",
ValidateAudience = true,
ValidAudience = "my-client",
ValidateLifetime = true,
ClockSkew = TimeSpan.FromMinutes(5)
};
});
}
Normally the claims of JWT are automatically added to the ClaimsIdentity.
Source:
https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/af5e5c2b0100e8348c63e2d2bb45612e2080841e/src/System.IdentityModel.Tokens.Jwt/JwtSecurityTokenHandler.cs#L1110).
So you should be able to just use 'User' property of the base 'Controller' class.
public async Task<IActionResult> Get()
{
// TODO Move 'Claims' extraction code to an extension method
var address = User.Claims.Where('GET THE NEEDED CLAIM');
...
}
I never had any problems getting the claims from a JWT token.
But I only used IdentityServer4.AccessTokenValidation so far. But internally it uses the Microsoft JWT Handler afaik.
Couldn't comment because my post would be to long, so making a seperate post instead.
If you follow the guide/link ErazerBrecht posted, the claims are indeed stored in the ClaimsPrincipal User. I created an extension method to retrieve the claim.
Note that I use an Enum for registering my claims. I use a dictionary to pass my claims to the method that generates my token, so my claim key should always be unique.
The extension method:
public static string GetClaim(this ClaimsPrincipal claimsPrincipal, JwtClaim jwtClaim)
{
var claim = claimsPrincipal.Claims.Where(c => c.Type == jwtClaim.ToString()).FirstOrDefault();
if (claim == null)
{
throw new JwtClaimNotFoundException(jwtClaim);
}
return claim.Value;
}
Call it like:
var userId = User.GetClaim(JwtClaim.UserId);