I'm using Swagger Swashbuckle in a dotnet core 3.1 web api project and have trouble to send bearer authorization to the requests calls.
I've defined this in my ConfigureServices method:
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo() { Title = "MyApi", Version = "v1" });
// Set the comments path for the Swagger JSON and UI.
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
c.IncludeXmlComments(xmlPath);
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
Type = SecuritySchemeType.OAuth2,
Flows = new OpenApiOAuthFlows()
{
Password = new OpenApiOAuthFlow()
{
TokenUrl = new Uri("/api/Account/Login", UriKind.Relative),
}
},
In = ParameterLocation.Header,
Name = "Authorization",
Scheme = "Bearer",
});
c.AddSecurityRequirement(new OpenApiSecurityRequirement()
{
{
new OpenApiSecurityScheme()
{
Reference = new OpenApiReference()
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
},
Scheme = "Bearer",
Type = SecuritySchemeType.Http,
Name = "Bearer",
In = ParameterLocation.Header
}, new List<string>()
}
});
});
When running, I see the Authorize button that show the login dialog:
Once logged, the API routes show the locked padlock but when I try to use them, I see the call is done without the returned bearer:
curl -X GET "http://localhost:5000/api/Account" -H "accept: */*" -H "Authorization: Bearer undefined"
What's wrong with my definitions?
In the cURL request you can see: -H "Authorization: Bearer undefined".
This means that when Swagger-UI tries to get the token that will be added to the request header, it cannot be found.
Then, where the token cames from, and why Swagger-UI cannot found it? The token comes in the json returned from your login endpoint (/api/Account/Login).
You must be sure that returned json from your login endpoint repect the expected format for a OAuth password flow, as explained in the RFC6749 section 4.1.4 (Access Token Response).
From your login endpoint you must return a json response like this:
{
"access_token":"2YotnFZFEjr1zCsicMWpAA",
"token_type":"bearer"
}
It's a common mistake, that when you serialize the response from the controller you dont respect the json property names. In example: you can be returning a json like this:
{
"accessToken":"2YotnFZFEjr1zCsicMWpAA",
"tokenType":"bearer"
}
Where "accessToken" is not the same as "access_token" and so on.
This little difference causes that Swagger-UI cannot found the bearer token when it deserializes the returned json.
TIP: decorate the "AccessToken" property of your response object, so it will be serialized correctly.
[JsonPropertyName("access_token")]
[JsonProperty(PropertyName = "access_token")]
public string AccessToken { get; set; }
Although what is explained is the main point of your problem, I want to tell you that adding the security requirement globally is not the most correct way to do it. In this way you are protecting all the endpoints, regardless of whether they are decorated with the Authorize attribute or not.
In the startup you only must set a "Security Definition", and add an "OperationFilter" that handles the "security requirements". Then "SecurityRequirement" references the "SecurityDefinition", then you don't repeat the security definition configuration (Scheme, Type, Name, In, etc.) inside the security requirements as you are doing in your example.
Refer to this github post that shows you the correct way to do it.
Related
Using this or https://nikiforovall.github.io/aspnetcore/dotnet/2022/08/24/dotnet-keycloak-auth.html tutorial I have setup test user and realm. I can call localhost:8080/realms/Test/protocol/openid-connect/token with client secret and user id and password from postman and it gives me access and refresh token. Now I need to call dotnet endpoint and make sure the user is who he is. But I can not find a way to establish this part as I'm always getting 401 unauthorized. Perhaps it is not setup or my authorization bearer string is not formed correctly.
How can I simply call to an endpoint, check authorization and return a response back?
Dotnet Code:
using System.Security.Claims;
using Api;
using Keycloak.AuthServices.Authentication;
using Keycloak.AuthServices.Authorization;
using Keycloak.AuthServices.Sdk.Admin;
var builder = WebApplication.CreateBuilder(args);
var services = builder.Services;
var configuration = builder.Configuration;
var host = builder.Host;
host.ConfigureLogger();
services
.AddEndpointsApiExplorer()
.AddSwagger();
var authenticationOptions = configuration
.GetSection(KeycloakAuthenticationOptions.Section)
.Get<KeycloakAuthenticationOptions>();
services.AddKeycloakAuthentication(authenticationOptions);
var authorizationOptions = configuration
.GetSection(KeycloakProtectionClientOptions.Section)
.Get<KeycloakProtectionClientOptions>();
services
.AddAuthorization(o => o.AddPolicy("IsAdmin", b =>
{
b.RequireResourceRoles("default-roles-test");
/*b.RequireRealmRoles("admin");
b.RequireResourceRoles("r-admin");
// TokenValidationParameters.RoleClaimType is overriden
// by KeycloakRolesClaimsTransformation
b.RequireRole("r-admin");*/
})
)
.AddKeycloakAuthorization(authorizationOptions);
var adminClientOptions = configuration
.GetSection(KeycloakAdminClientOptions.Section)
.Get<KeycloakAdminClientOptions>();
services.AddKeycloakAdminHttpClient(adminClientOptions);
var app = builder.Build();
app
.UseSwagger()
.UseSwaggerUI();
app.UseAuthentication();
app.UseAuthorization();
app.MapGet("/", (ClaimsPrincipal user) =>
{
// TokenValidationParameters.NameClaimType is overriden based on keycloak specific claim
app.Logger.LogInformation("{#User}", user.Identity.Name);
return "Hello world. "+ user.Identity.Name;
}).RequireAuthorization("IsAdmin");
app.Run();
appsettings.json keycloack config:
"Keycloak": {
"realm": "Test",
"auth-server-url": "http://localhost:8080/",
"ssl-required": "none",
"resource": "test-client",
"verify-token-audience": false,
"client-secret": "P4JgvFhjY0ftGSLDYmYn7diZhjoLnHon",
"confidential-port": 0
}
Request sending to this endpoint from postman (perhaps the issue is here with correct sending format):
I am building an ASP.NET (v4.8) Web application that will be hosted as an Azure App Service, but for now we are on localhost.
I am configured for Azure AD successfully and I am receiving an authorization code because I configured my app service to send the access token. The app registration has ONLY User.Read (delegated) permissions.
In my Startup.cs file, I've configured OpenIdConnectAuthenticationNotifications so that I am receiving the access code in AuthorizationCodeReceived. Here is the code:
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType( CookieAuthenticationDefaults.AuthenticationType );
app.UseCookieAuthentication(new CookieAuthenticationOptions());
authority = aadInstance + tenantId;
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions {
ClientId = clientId, Authority = authority,
PostLogoutRedirectUri = postLogoutRedirectUri,
Notifications = new OpenIdConnectAuthenticationNotifications()
{
TokenResponseReceived = (tr) => { return Task.FromResult(0); },
AuthorizationCodeReceived = (code) => {
// you are here! what's next?
access_code = code.Code;
return Task.FromResult(0);
},
SecurityTokenReceived = (token) =>
{
return Task.FromResult(0);
},
AuthenticationFailed = (context) => { return System.Threading.Tasks.Task.FromResult(0); }
}
});
app.UseStageMarker(PipelineStage.Authenticate);
}
My objective is to call this graph endpoint as the current user to get their JobTitle and > Department from Azure AD. Here is the resource: https://graph.microsoft.com/v1.0/me
I was following this documentation, but it was not clear what to do with the provided access_code. Please help me understand.
Is this access_code a bearer token? can I use it directly to call the graph API?
Do I have to use it to call the /token endpoint to get a bearer token?
Do I have to use it to call the /authorize endpoint to get a bearer token?
I am making direct HTTP requests now, should I use MSAL or Graph SDK?
I think I am trying to accomplish this step:
This is the code I am currently working on, and it returns HTTP CODE 400 (Bad Request):
private void GetOtherProfileData()
{
var cId = Startup.clientId;
var tenantId = Startup.tenantId;
var scope = Startup.scope;
// scope: https%3A%2F%2Fgraph.microsoft.com%2Fuser.read
var code = Startup.access_code;
var redir = HttpUtility.UrlEncode(Startup.redirectUri);
var req_url = $#"https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token?client_id={cId}&scope={scope}
&code={code}&redirect_uri={redir}&grant_type=authorization_code
&code_verifier=ThisIsntRandomButItNeedsToBe43CharactersLong";
var req = WebRequest.CreateHttp(req_url);
req.Method = "POST";
req.ContentLength = 0;
req.ContentType = "application/x-www-form-urlencoded";
var resp = req.GetResponse();
var str = resp.GetResponseStream();
var json = new StreamReader(str).ReadToEnd();
Trace.TraceInformation(json);
/// this should return bearer token and then we go call the /me endpoint...
///right?
}
Any code samples or pointers to recent documentation would be helpful.
Is this access_code a bearer token? can I use it directly to call the graph API?
No, code and Access_token are different. You will need a access_token to call Graph API.
Do I have to use it to call the /token endpoint to get a bearer token?
Yes, you'll need code to call token endpoint to get the bearer token.
Do I have to use it to call the /authorize endpoint to get a bearer token?
You will get the code after calling authorize endpoint. You need to pass grant_type=code to get the code in response.
I am making direct HTTP requests now, should I use MSAL or Graph SDK?
You'll need to call Graph API after you get the access_token. Along with the token it also needs proper dedicated and application User permissions from Azure side.
I found a good sample here: https://github.com/Azure-Samples/ms-identity-aspnet-webapp-openidconnect
I was trying to do this without a client secret, that was a mistake.
This is how I implemented it =>
AuthorizationCodeReceived = async (context) => {
// you are here!
IConfidentialClientApplication clientApp = MsalAppBuilder.BuildConfidentialClientApplication();
AuthenticationResult result = await clientApp.AcquireTokenByAuthorizationCode(new[] { "User.Read" }, context.Code)
.WithSpaAuthorizationCode() //Request an authcode for the front end
.ExecuteAsync();
access_code = result.AccessToken;
// this is the bearer token.
},
This is what is inside the implementation of BuildConfidentialClientApplication:
clientapp = ConfidentialClientApplicationBuilder.Create(Startup.clientId)
.WithClientSecret(Startup.secret)
.WithRedirectUri(Startup.redirectUri)
.WithAuthority(new Uri(Startup.authority))
.Build();
I have a native window app program,and want access API of gitlab service,hope to get token using name and password with using POCO Library and OAuth2,but I don't know how to send Get and Post request to gitlab with OAuth2,and using POCO Library, please give me a example.
Here is the data that needs to be sent and received.
Requesting access token
POST request to /oauth/token with parameters:
{
"grant_type" : "password",
"username" : "user#example.com",
"password" : "secret"
}
Then, you'll receive the access token back in the response:
{
"access_token": "1f0af717251950dbd4d73154fdf0a474a5c5119adad999683f5b450c460726aa",
"token_type": "bearer",
"expires_in": 7200
}
First you need to create a HTTPRequest object like so:
Poco::Net::HTTPClientSession* session = Poco::Net::HTTPSessionFactory::defaultFactory().createClientSession(serverUri);
Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_POST, serverUri.getPathAndQuery(), Poco::Net::HTTPMessage::HTTP_1_1);
Then create a HTMLForm:
Poco::Net::HTMLForm form;
form.add("grant_type", "password");
form.add("client_id", "client token");
form.add("client_secret", "client secret");
form.add("username", "user#example.com");
form.add("password", "secret");
form.prepareSubmit(request);
Send the request and write the form data into the output stream of the request:
std::ostream& requestStream = session->sendRequest(request);
form.write(requestStream);
Get the response out of the session:
Poco::Net::HTTPResponse response;
std::istream& responseStream = session->receiveResponse(response);
std::stringstream rawJson;
Poco::StreamCopier::copyStream(responseStream, rawJson);
Parse the raw JSON:
Poco::JSON::Parser parser;
Poco::JSON::Object::Ptr authObj = parser.parse(rawJson).extract<Poco::JSON::Object::Ptr>();
Create a new session for next request and attach an authorization header to the request:
Poco::Net::HTTPClientSession* dataSession = Poco::Net::HTTPSessionFactory::defaultFactory().createClientSession(dataUri);
Poco::Net::HTTPRequest dataRequest(Poco::Net::HTTPRequest::HTTP_GET, dataUri.getPathAndQuery(), Poco::Net::HTTPMessage::HTTP_1_1);
dataRequest.add("Authorization", "Bearer " + authObj->get("access_token"));
dataSession->sendRequest(dataRequest);
Get the response and read the data from stream:
std::stringstream data;
Poco::Net::HTTPResponse dataResponse;
Poco::StreamCopier::copyStream(dataSession->receiveResponse(dataResponse), data);
Hope it helps or points into the right direction.
My client get token from IdentityServer4 and give it to my API to get service,
all thing goes true until i want to get user claim (which STS automatically put on the user claim), when i call User.Identity.Name, it return null.
Accroding to the This Link i should define Scope in the API startup, so i define:
app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
{
Authority = "http://localhost:33934",
RequireHttpsMetadata = false,
ApiName = "api1",
AllowedScopes = { "profile", "email", "api1" },
});
All thing goes right, my user can get Token from IdentityServer, and when i pass it to API, the Authorization will done successfully, but the User.Identity.Name return null!
what is the problem?
You can print out the list of claims using
<dl>
#foreach (var claim in User.Claims)
{
<dt>#claim.Type</dt>
<dd>#claim.Value</dd>
}
</dl>
Try this on the client app and you will know if you are getting value in name or not.
I have taken section 2 from a walkthrough on how to authorize with jwt so that I can get an access token from my client and authorize them to use the api. However, I can't seem to get this to work. I keep on getting a 401 message from Postman accompanied by a:
{
"Message": "Authorization has been denied for this request."
}
Tutorial Link: http://bitoftech.net/2014/10/27/json-web-token-asp-net-web-api-2-jwt-owin-authorization-server/
The access token comes from an authorization service I have created in Java, so the first section of the tutorial does not apply to me.
JWT
{
"exp": 1489641048,
"user_name": "testuser",
"authorities": [
"USER"
],
"jti": "2dde11c3-2f06-496c-9b36-4dbf71cdc2e2",
"client_id": "webreport_service",
"scope": [
"USER"
]
}
Web API code snippet
public void ConfigureOAuth(IAppBuilder app)
{
var audience = "webreport_service";
// Api controllers with an [Authorize] attribute will be validated with JWT
app.UseJwtBearerAuthentication(
new JwtBearerAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Active,
AllowedAudiences = new[] { audience },
IssuerSecurityTokenProviders = new IIssuerSecurityTokenProvider[]
{
new SymmetricKeyIssuerSecurityTokenProvider("Any", "abc123")
}
});
}
It is slightly different from what is on the link in section 2, but that is because I don't base64 encode my secret and I also do not put the issuer in my jwt.
Postman
GET /api/protected HTTP/1.1
Host: localhost:54706
Authenticate: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0ODk2NDEyOTAsInVzZXJfbmFtZSI6InRlc3R1c2VyIiwiYXV0aG9yaXRpZXMiOlsiVVNFUiJdLCJqdGkiOiJlMDNkNWZmZC1hZWI4LTRkODctOGQ3My0zNjhjYjQ2ZDg2OWUiLCJjbGllbnRfaWQiOiJ3ZWJyZXBvcnRfc2VydmljZSIsInNjb3BlIjpbIlVTRVIiXX0.C4hivwA1VF-0GO0xCVUoDIheWQWlAcVWvAzChZTgrHY
Cache-Control: no-cache
Postman-Token: ff628109-d5f4-76e0-41c2-e0c7d377b93f
Any help would be greatly appreciated.
Thanks!
I think so check the following:
Firstly:
Check your secret base64 code. 'abc123' is true?
I'm check your token in jwt.io website on your secret code.
but Invalid Signature
Secondly:
Check your payload value.
What is 'iss' your jwt payload. / your issuer validate set 'Any'
What is 'aud' your jwt payload. / your audience validate set 'webreport_service'
Think about it.
Best regard