I have a simple Dotnet core API that gets value1,value2 and the method is [Authorize].
I am trying to retrieve token back from Cognito idp in order to access the result but the thing is I couldn't find a way to send the (username, pass, and email)to Cognito in order to get back a token.
I keep getting the following error:
Error in SAML response processing: Invalid user attributes: email: The attribute is required ', error_uri: 'error_uri is null'
ยจ
In startup.cs I configured it as the following:
services.Configure<OpenIdConnectOptions>(Configuration.GetSection("Authentication:Cognito"));
var serviceProvider = services.BuildServiceProvider();
var authOptions = serviceProvider.GetService<IOptions<OpenIdConnectOptions>>();
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect(options =>
{
options.ResponseType = authOptions.Value.ResponseType;
options.MetadataAddress = authOptions.Value.MetadataAddress;
options.ClientId = authOptions.Value.ClientId;
options.ClientSecret = authOptions.Value.ClientSecret;
options.SaveTokens = authOptions.Value.SaveTokens;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = authOptions.Value.TokenValidationParameters.ValidateIssuer
};
});
I am expecting to get the token back after sending (username, pass, and email) attributes to Cognito idp and provide it to postman in order to get the values.
Use the below code to retrieve the access token after successful sign up with username and password
CognitoUserPool userPool=new CognitoUserPool(poolid,client_id,provider);
CognitoUser user=new CognitoUser(username,client_id,userPool,provider);
InitiateSrpAuthRequest authRequest=new InitiateSrpAuthRequest()
{
Password=password
};
Task<AuthFlowResponse> authFlowResponse=null;
authFlowResponse=user.StartWithSrpAuthAsync(authRequest);
string Token=authFlowResponse.Result.AuthenticationResult.AccessToken.ToString();
Related
I am building a Blazor Server ASP.NET Core application with cookie based authentication through a OpenID Connect (OIDC) provider. On sign-out I want to be redirected to localhost URI: https://localhost:44378/signout-oidc. This is the path registrered as Post Logout Redirect URI at the OIDC Provider.
When I sign-out I am sent through the sign-out flow at the Connect provider, their log states "End session request validation success", but I end up on the URI: https://localhost:44378/signout-oidc?state=CfDJ8LdQ[...] which is a blank page. If I try to access https://localhost:44378/signout-oidc while I am logged in I get Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectHandler: Error: The remote signout request was ignored because the 'sid' parameter was missing, which may indicate an unsolicited logout.
I have tried a myriad of different combinations of overriding the SignedOutRedirectUri, SignedOutCallbackPath, RemoteSignOutPath, etc., as well as redirecting to other pages - all to no avail. I am running out of ideas and would appreciate all kinds of input.
I have added the code for the configuration of OpenID authentication:
services.AddAuthentication(opt =>
{
opt.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
opt.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
opt.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
}).AddCookie("Cookies", options =>
{
options.Cookie.SameSite = SameSiteMode.None;
})
.AddOpenIdConnect("oidc", options =>
{
options.Authority = configuration.GetSection("AuthorizationStrings")["Authority"];
options.ClientId = configuration.GetSection("AuthorizationStrings")["ClientId"];
options.ClientSecret = configuration.GetSection("AuthorizationStrings")["ClientSecret"];
options.ResponseType = "code";
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.UseTokenLifetime = false;
options.Scope.Add("openid");
options.Scope.Add("profile");
options.TokenValidationParameters = new TokenValidationParameters { NameClaimType = "name" };
options.SignedOutCallbackPath = "/signout-oidc";
Code for the sign-out flow which is initiated through a sign-out button. The formatting of the Redirect URI is how the OIDC provider expects it (the state parameter is optional so I have left it out):
public async Task OnGetAsync()
{
var ac = await HttpContext.GetTokenAsync("access_token");
String uri = String.Format("[CONNECT PROVIDER URI]/endsession?id_token_hint={0}&post_logout_redirect_uri=https://localhost:44378/signout-oidc", ac);
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
var prop = new AuthenticationProperties
{
RedirectUri = uri
};
await HttpContext.SignOutAsync("oidc", prop);
}
You might want to set the LogoutPath, as it must match the URL of the "Logout" link; if they do match, then the RedirectUri will be accepted.
}).AddCookie(options =>
{
options.LogoutPath = "/User/Logout";
...
});
See this answer here for more details.
This is my Logout code for /User/Logout
/// <summary>
/// Do the logout
/// </summary>
/// <returns></returns>
[HttpPost]
[ValidateAntiForgeryToken]
public async Task Logout()
{
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
await HttpContext.SignOutAsync(OpenIdConnectDefaults.AuthenticationScheme);
//Important, this method should never return anything.
}
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 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'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);
I am looking a way to grab the value of Claim Issuer. I want to make 2 way validation of token.
First step user will get the token from System API and pass it to Customer API. In customer API token need to be validate with system API token parameter.
if it is validated customer api generate a new token to the user and user will use the new token in next steps.
I am doing this because in the second step I will add some claim for only that user have to see and use. like connection string, user role for its own database.
Or is there any other way to do like this verification ?
thanks.
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
if (????)
{
// system token validator
}
else
{
//customer token validator
}
});
Found a way to handle the problem
Just add two claim issuer and two key to validation parameter so I don't need to check anymore the claim issuer
var issuers = new List<string>()
{
Configuration["SystemToken:Issuer"],
Configuration["CustomerToken:Issuer"]
};
SecurityKey SystemKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["SystemToken:Key"]));
SecurityKey CustomerKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["CustomerToken:Key"]));
options.TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuer = true,
ValidIssuers = issuers,
ValidateAudience = false,
ValidateIssuerSigningKey = true,
IssuerSigningKeys = new List<SecurityKey>() { SystemKey, CustomerKey },
ValidateLifetime = true
};
thanks you for helping
#Tratcher
https://github.com/aspnet/Security/issues/1604