'Invalid token' when using webapi thru Swagger authenticated by Azure AD - .net-core

I try to use AAD authentification on my WebApi (dotnet core 3.1) from swagger (Swashbuckle) client.
In my Startup class, I've configured like follow:
// Configure authentication
services.AddAuthentication(AzureADDefaults.JwtBearerAuthenticationScheme)
.AddAzureADBearer(options => Configuration.Bind("AzureAd", options));
my settings for AzureAd:
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"ClientId": "xxxx-xxxxx1b",
"Domain": "myoffice.onmicrosoft.com",
"TenantId": "xxxxx-xxxxa5",
"Scope": "api://xxxxxxxx-abc3cff48f1b/Full.Access",
"ScopeDescription": "Full Access"
},
...
services.AddSwaggerGen(c =>
{
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme()
{
Type = SecuritySchemeType.OAuth2,
In = ParameterLocation.Header,
Flows = new OpenApiOAuthFlows()
{
Implicit = new OpenApiOAuthFlow
{
TokenUrl = new Uri($"Configuration["AzureAd:Instance"]}/{Configuration["AzureAd:TenantId"]}/oauth2/v2.0/token"),
AuthorizationUrl = new Uri($"{Configuration["AzureAd:Instance"]}/{Configuration["AzureAd:TenantId"]}/oauth2/v2.0/authorize"),
Scopes =
{
{
Configuration["AzureAd:Scope"],Configuration["AzureAd:ScopeDescription"]
}
}
}
}
});
c.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
}
},
Array.Empty<string>()
}
});
});
And in my Configure method:
app.UseSwaggerUI(c =>
{
c.RoutePrefix = string.Empty;
c.SwaggerEndpoint($"/swagger/{ApiVersion}/swagger.json", ApiName);
c.OAuthClientId(Configuration["AzureAd:ClientId"]);
c.OAuthScopeSeparator(" ");
});
Swagger corectly log to AAD using my credential and when I use a route protected by [Authorize] the token is correcly sent to the API by I receive a 401 error with the following message:
www-authenticate: Bearer error="invalid_token"error_description="The issuer 'https://login.microsoftonline.com/{tenantid}/v2.0' is invalid"
The url https://login.microsoftonline.com/{tenantid}/v2.0 is in the token in the iss section.
What is wrong?

According to your error and your code, you do not tell your application the ValidIssuer. So you get the error. Please add the following code in the method ConfigureServices of startup.cs file
services.Configure<JwtBearerOptions>(AzureADDefaults.JwtBearerAuthenticationScheme, options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidIssuers = new[] {
},
});
For example
Configure Azure AD for your web API. For more details, please refer to the document
a. Create Azure AD web api application
b. Expose API
c. Configure code
config file
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"ClientId": "[Client_id-of-web-api-eg-2ec40e65-ba09-4853-bcde-bcb60029e596]",
"TenantId": "<your tenant id>"
},
Add following code in the Stratup.cs
services.AddAuthentication(AzureADDefaults.BearerAuthenticationScheme)
.AddAzureADBearer(options => Configuration.Bind("AzureAd", options));
services.Configure<JwtBearerOptions>(AzureADDefaults.JwtBearerAuthenticationScheme, options =>
{
options.Authority += "/v2.0";
options.TokenValidationParameters = new TokenValidationParameters
{
/**
* with the single-tenant application, you can configure your issuers
* with the multiple-tenant application, please set ValidateIssuer as false to disable issuer validation
*/
ValidIssuers = new[] {
$"https://sts.windows.net/{Configuration["AzureAD:TenantId"]}/",
$"https://login.microsoftonline.com/{Configuration["AzureAD:TenantId"]}/v2.0"
},
ValidAudiences = new[]
{
options.Audience,
$"api://{options.Audience}"
}
};
});
Configure swagger. For more details, please refer to the blog.
a. Create Azure Web application
b. Configure API permissions. Regarding how to configure, you can refer to the document
c. code
Install SDK
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.5.1" />
config file
"Swagger": {
"ClientId": ""
},
Add the following code to Startup.cs in the ConfigureServices method:
services.AddSwaggerGen(o =>
{
// Setup our document's basic info
o.SwaggerDoc("v1", new OpenApiInfo
{
Title = "Protected Api",
Version = "1.0"
});
// Define that the API requires OAuth 2 tokens
o.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme
{
Type = SecuritySchemeType.OAuth2,
Flows = new OpenApiOAuthFlows
{
Implicit = new OpenApiOAuthFlow
{
Scopes = new Dictionary<string, string>
{
{ "api://872ebcec-c24a-4399-835a-201cdaf7d68b/user_impersonation","allow user to access api"}
},
AuthorizationUrl = new Uri($"https://login.microsoftonline.com/{Configuration["AzureAD:TenantId"]}/oauth2/v2.0/authorize"),
TokenUrl = new Uri($"https://login.microsoftonline.com/{Configuration["AzureAD:TenantId"]}/oauth2/v2.0/token")
}
}
});
o.AddSecurityRequirement(new OpenApiSecurityRequirement{
{
new OpenApiSecurityScheme{
Reference = new OpenApiReference{
Id = "oauth2",
Type = ReferenceType.SecurityScheme
}
},new List<string>()
}
});
});
Add the following code to the Configure method:
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.OAuthClientId(Configuration["Swagger:ClientId"]);
c.OAuthScopeSeparator(" ");
c.OAuthAppName("Protected Api");
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
});
Test

Related

Integrate Google OpenID with Swagger in .NET 6

I want to authenticate in my application using Google OpenID. I added authentication in Swagger, but after successfully redirection, login and redirection back to my Swagger, requests are only send access_token in header, but not id_token which is supposed to include e.g. user email (I used https://www.googleapis.com/auth/userinfo.email scope). Is there an option to get this id_token to correctly authenticate in .NET app? I'm using implicit flow right now. There is my Swagger configuration:
services.AddSwaggerGen(c =>
{
c.AddSecurityRequirement(new OpenApiSecurityRequirement() {
{
new OpenApiSecurityScheme {
Reference = new OpenApiReference {
Type = ReferenceType.SecurityScheme,
Id = "oauth2"
},
Scheme = "oauth2",
Name = "authorization",
In = ParameterLocation.Header
},
new List <string> ()
}
});
c.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme
{
Type = SecuritySchemeType.OAuth2,
Flows = new OpenApiOAuthFlows
{
Implicit= new OpenApiOAuthFlow
{
AuthorizationUrl = new Uri($"https://accounts.google.com/o/oauth2/auth"),
TokenUrl = new Uri($"https://oauth2.googleapis.com/token"),
Scopes = new Dictionary<string, string>
{
{
$"https://www.googleapis.com/auth/userinfo.email",
"Get email"
}
}
}
}
});
The request looks following:
Request with access_token
I read that there is a possibility to change default authorization parameter in Swagger. So after adding following configuration:
Extensions = new Dictionary<string, IOpenApiExtension>
{
{
"x-tokenName", new OpenApiString("id_token")
}
}
result was still inappropriate:
Request with id_token

Swagger OAuth, how to send bearer token in a different header than "Authorization"

I have an API with authorization code flow authentication and I have configured swagger to use that as a security definition and it works fine.
Now I need swagger to send the bearer token in a different header as well, besides "Authorization", e.g. "X-Forwarded-Authorization". Is there a way to do that?
My current security configuration:
setup.AddSecurityDefinition(
"oauth2", new OpenApiSecurityScheme
{
Type = SecuritySchemeType.OAuth2,
Flows = new OpenApiOAuthFlows
{
AuthorizationCode = new OpenApiOAuthFlow
{
AuthorizationUrl = new Uri("..."),
TokenUrl = new Uri("..."),
Scopes = { }
}
},
});
You can configure swagger when adding service collection like this;
services.AddSwaggerGen(options =>
{
//...other configurations
options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
In = ParameterLocation.Header,
Description = "Please insert JWT with Bearer into field!",
Name = "X-Forwarded-Authorization",
Type = SecuritySchemeType.ApiKey
});
options.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
}
}, Array.Empty<string>()
}
});
//...other configurations
});

Firebase google auth in Swagger (Swashbuckle.AspNetCore)

Trying to implement firebase google authentication in the swagger
onboard: asp.net core 5, Swashbuckle.AspNetCore 6.3.1,
In fairbase console > authentication > Sign-in method > authentication via google is enabled
On ServiceConfigure method:
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "MyApi", Version = "v1" });
c.UseInlineDefinitionsForEnums();
c.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme
{
Type = SecuritySchemeType.OAuth2,
Flows = new OpenApiOAuthFlows
{
//email and password authentication - works fine
Password = new OpenApiOAuthFlow
{
TokenUrl = new Uri("/api/v1/auth/password", UriKind.Relative), //here my backend endpoint
Extensions = new Dictionary<string, IOpenApiExtension>
{
{ "returnSecureToken", new OpenApiBoolean(true) },
},
},
//try add google auth - troble here
Implicit = new OpenApiOAuthFlow()
{
//Not sure about the endpoints. Its not work with 404 err
AuthorizationUrl = new Uri("https://securetoken.google.com/MY-PROJECT-FIREBASE-NAME"),
TokenUrl = new Uri("https://securetoken.google.com/MY-PROJECT-FIREBASE-NAME"),
Scopes = new Dictionary<string, string>
{
{ "profile", "profile" },
}
}
}
});
c.OperationFilter<AuthorizeCheckOperationFilter>();
});
class filter:
public class AuthorizeCheckOperationFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
var requiredScopes = context.MethodInfo.DeclaringType.GetCustomAttributes(true)
.OfType<AuthorizeAttribute>()
.Select(attr => attr.Policy)
.Distinct();
if (requiredScopes.Any())
{
var oAuthScheme = new OpenApiSecurityScheme
{
Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "oauth2" }
};
operation.Security = new List<OpenApiSecurityRequirement>
{
new OpenApiSecurityRequirement
{
[ oAuthScheme ] = requiredScopes.ToList()
}
};
}
}
}
in Configure method:
app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "MyApi v1");
What endpoints for AuthorizationUrl / TokenUrl should call?
Any additinal options for swagger?
I`m new in Firebase. M.b. need aditional adjustments in firebase console?
I would be very appreciate for a code sample.
I found a good repo for you. This simple is .NET 5 WebApi project base with the following features implemented:
Swagger with Firebase login
Serilog Logging to Amazon CloudWatch
Encrypted fields in AppSettings
Soft Delete and Audit Columns
Multitenancy support
Data Seeding
Localization
code sample:clean-base-api

Blazor IdentityServer Authenthentication

Currently I have three separate servers. Client on :5001, API on :5002 and IdentityServer on :5003. I can athenticate my Blazor pages using #attribute [Authorize] but when I call the API I get a 401 error. If I past the token_id into postman and make a request to the API server it authenticates. If I make a request from my Blazor client it fails. I have whitelisted CORS to rule out that being the issue. If I remove the Audience check on the api with:
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false
};
It works
Client program.cs
builder.Services.AddHttpClient("api")
.AddHttpMessageHandler(sp =>
{
var handler = sp.GetService<AuthorizationMessageHandler>()
.ConfigureHandler(
authorizedUrls: new[] { "https://localhost:5002" },
scopes: new[] { "coredesk" });
return handler;
});
builder.Services.AddScoped(
sp => sp.GetService<IHttpClientFactory>().CreateClient("api"));
builder.Services.AddOidcAuthentication(options =>
{
builder.Configuration.Bind("oidc", options.ProviderOptions);
});
Client appsettings.json
{
"oidc": {
"Authority": "https://localhost:5003/",
"ClientId": "coredesk",
"DefaultScopes": [
"openid",
"profile",
"coredesk"
],
"PostLogoutRedirectUri": "/",
"ResponseType": "code"
}
}
API Startup.cs
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = "https://localhost:5003";
options.Audience = "coredesk";
});
IdentityServer Config.cs
public static IEnumerable<IdentityResource> IdentityResources =>
new IdentityResource[]
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
};
public static IEnumerable<ApiResource> Apis =>
new ApiResource[]
{
new ApiResource("coredesk", "CoreDesk API")
};
public static IEnumerable<ApiScope> ApiScopes =>
new ApiScope[]
{
new ApiScope("coredesk"),
};
public static IEnumerable<Client> Clients =>
new Client[]
{
new Client
{
ClientId = "coredesk",
AllowedGrantTypes = GrantTypes.Code,
RequirePkce = true,
RequireClientSecret = false,
AllowedCorsOrigins = { "https://localhost:5001", "https://localhost:5002" },
AllowedScopes = { "openid", "profile", "coredesk" },
RedirectUris = { "https://localhost:5001/authentication/login-callback" },
PostLogoutRedirectUris = { "https://localhost:5001/" },
Enabled = true
},
};
if you look at the source code for AddJwtBearer you find this part:
// If no authorization header found, nothing to process further
if (string.IsNullOrEmpty(authorization))
{
return AuthenticateResult.NoResult();
}
if (authorization.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
{
token = authorization.Substring("Bearer ".Length).Trim();
}
// If no token found, no further work possible
if (string.IsNullOrEmpty(token))
{
return AuthenticateResult.NoResult();
}
this shows that by default (without customization) it requires the token to be provided in the Authorization header, like this:
GET /api/payments HTTP/1.1
Host: localhost:7001
Authorization: Bearer eyJhbGciOiJSUzI1NiIsIYwA...
to further debug, I would remove the authorize attribute and then put a breakpoint in one of the action method and examine what the ClaimsPrincipal user object contains.

Automatically Attaching Identity Cookie to HTTP Client in Blazor wasm

I am working on a blazor application where I used my API project as Identity
Provider. Everything is working fine but the issue is that the access token
issued by my API is not validated by the API. It turns out the API is expecting a
cookie header. I took a closer look at blazor hosted application and found out
the cookie is being sent along with each request but it's same-origin.
My Blazor WASM project does not automatically attach this cookie in the request
header, just the access token.
Is there a way I can make the Http handler attach this cookie on each request?
or make the API validate the access token instead of the identity cookie.
This is my startup class in the API Project
public static void AddIdentityServer(IServiceCollection services,IConfiguration configuration)
{
services.AddIdentityServer(options =>
{
options.UserInteraction.LoginUrl = "/Identity/Account/Login";
options.UserInteraction.LogoutUrl = "/Identity/Account/Logout";
}).AddProfileService<LocalProfileService>()
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>(option =>
{
option.Clients.Add(new Client
{
ClientId = "blazor",
AllowedGrantTypes = GrantTypes.Code,
RequirePkce = true,
RequireClientSecret = false,
AllowedCorsOrigins = { "https://localhost:5001" },
AllowedScopes = { "openid", "profile", "email","id" },
RedirectUris = { "https://localhost:5001/authentication/login-callback" },
PostLogoutRedirectUris = { "https://localhost:5001/" },
Enabled = true,
RequireConsent = false,
});
option.IdentityResources.AddEmail();
option.IdentityResources["openid"].UserClaims.Add("name");
option.ApiResources.Single().UserClaims.Add("name");
option.IdentityResources["openid"].UserClaims.Add("role");
option.ApiResources.Single().UserClaims.Add("role");
option.IdentityResources.Add(new IdentityResource("id",new string[] {"id" }));
option.ApiResources.Single().UserClaims.Add("id");
});
services.AddAuthentication()
.AddGoogle("Google", options =>
{
options.ClientId = configuration["ExternalLoginApiKey:GoogleClientId"];
options.ClientSecret = configuration["ExternalLoginApiKey:GoogleClientSecret"];
})
.AddFacebook("Facebook", options =>
{
options.AppId = configuration["ExternalLoginApiKey:FacebookAppId"];
options.AppSecret = configuration["ExternalLoginApiKey:FacebookAppSecret"];
})
.AddIdentityServerJwt();
}
Program class in the Blazor Project
public static async Task Main(string[] args)
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("app");
builder.Services.AddOidcAuthentication(options =>
{
builder.Configuration.Bind("oidc", options.ProviderOptions);
options.UserOptions.RoleClaim = "role";
}).AddAccountClaimsPrincipalFactory<CustomUserFactory>();
builder.Services.AddHttpClient<IAuthorizedRestService, AuthorizedRestService>(
client => client.BaseAddress = new Uri("https://localhost:5002/api/mart/v1/"))
.AddHttpMessageHandler(sp => sp.GetRequiredService<AuthorizationMessageHandler>()
.ConfigureHandler(authorizedUrls: new[] { "https://localhost:5002" }));
builder.Services.AddHttpClient("noauth", option => option.BaseAddress = new
Uri("https://localhost:5002/api/mart/v1/"));
builder.Services.AddScoped<IRestService, RestService>();
await builder.Build().RunAsync();
}
I have found the Solution.
It happens that there is already a JWT handler provided by IdentityServer4 for APIs that double as Authorization Server
.AddIdentityServerJwt();
So what I did was to configure it
services.Configure<JwtBearerOptions>
(IdentityServerJwtConstants.IdentityServerJwtBearerScheme,
options =>
{
options.Authority = "https://localhost:5002";
options.Audience = "mart";
options.SaveToken = true;
});
Then specify the Authentication scheme to use
[Authorize(AuthenticationSchemes = IdentityServerJwtConstants.IdentityServerJwtBearerScheme)]
You can also add it globally in the start up class
var authorizationPolicy = new AuthorizationPolicyBuilder(IdentityServerJwtConstants.IdentityServerJwtBearerScheme)
.RequireAuthenticatedUser().Build();
options.Filters.Add(new AuthorizeFilter(authorizationPolicy));
You can read more using these links
https://learn.microsoft.com/en-us/aspnet/core/security/authorization/limitingidentitybyscheme?view=aspnetcore-3.1
https://learn.microsoft.com/en-us/aspnet/core/security/authentication/identity-api-authorization?view=aspnetcore-3.1

Resources