Using POSTMAN, I'm struggling to to retrieve my Identity Server 3 token.
Error code is : 400 Bad Request
Here are the details:
POST /identity/connect/token HTTP/1.1
Host: localhost:44358
Content-Type: application;x-www-form-urlencoded
Cache-Control: no-cache
Postman-Token: 57fc7aef-0006-81b2-8bf8-8d46b77d21d1
username=MYUSER-ID&password=MY-PASSWORD&grant_type=password&client_id=rzrwebguiangulajsclient&client_secret=myclientsecret&redirect_uri=https://localhost:44331/callback
I've done something similar with a simple Visual Studio 2015 WebApi project, where the end point was \token.
Any guidance/advice is appreciated...
regards,
Bob
The minimum required for a Resource Owner OAuth request is the following (line breaks added for readability):
POST /connect/token
Header
Content-Type: application/x-www-form-urlencoded
Body
username=MYUSER-ID
&password=MY-PASSWORD
&grant_type=password
&client_id=rzrwebguiangulajsclient
&client_secret=myclientsecret
&scope=api
Off the bat you are not requesting a scope in your request. Otherwise there is most probably something wrong in the configuration of your client within Identity Server.
Your best bet would be to enable logging and look at what comes back when this request errors.
Update: also, please don't use the ROPC grant type
I'm happy to say that we got Postman to work.
It turns out I was so close to getting Postman to work with Identity Server 3 Authorization.
The final piece to the solution was setting the Postman client Flow to Flow = Flows.ClientCredentials (see the postmantestclient client definition below):
using System.Collections.Generic;
using IdentityServer3.Core.Models;
namespace MyWebApi.MyIdentityServer.Config
{
public static class Clients
{
public static IEnumerable<Client> Get()
{
return new[]
{
new Client
{
ClientId = MyConstants.MyIdentityServer.MyWebGuiClientId,
ClientName = "My Web Gui Client",
Flow = Flows.Implicit,
AllowAccessToAllScopes = true,
IdentityTokenLifetime = 300,
AccessTokenLifetime = 300, //5 minutes
RequireConsent = false,
// redirect = URI of the Angular application
RedirectUris = new List<string>
{
MyConstants.MyIdentityServer.MyWebGuiUri + "callback.html",
// for silent refresh
MyConstants.MyIdentityServer.MyWebGuiUri + "silentrefreshframe.html"
},
PostLogoutRedirectUris = new List<string>()
{
MyConstants.MyIdentityServer.MyWebGuiUri + "index.html"
}
},
new Client
{
ClientId = MyConstants.MyIdentityServer.SwaggerClientId,
ClientName = "Swagger Client",
Flow = Flows.Implicit,
AllowAccessToAllScopes = true,
IdentityTokenLifetime = 300,
AccessTokenLifetime = 300,
RequireConsent = false,
// redirect = URI of the Angular application
RedirectUris = new List<string>
{
"https://localhost:44358/swagger/ui/o2c-html"
}
},
new Client
{
ClientId = "postmantestclient",
ClientName = "Postman http test client",
Flow = Flows.ClientCredentials,
AllowAccessToAllScopes = true,
IdentityTokenLifetime = 300,
AccessTokenLifetime = 300, //5 minutes
RequireConsent = false,
ClientSecrets = new List<Secret>
{
new Secret("PostmanSecret".Sha256())
},
RedirectUris = new List<string>()
{
"https://www.getpostman.com/oauth2/callback"
}
}
};
}
}
}
Related
I have lambda connected to API GW HTTP API.
If my method looks like this:
public async Task<APIGatewayHttpApiV2ProxyResponse> WebhookInternal(APIGatewayHttpApiV2ProxyRequest request) {
return new APIGatewayHttpApiV2ProxyResponse()
{
StatusCode = 200,
Body = new {
Status = 200,
Message = "OK",
}.ToJson()
};
}
everything works.
But I want to be able to keep the lambda warm by using scheduled events. So I needed to have the method more general. Therefore I changed its signature to handle Stream:
public async Task<Stream> WebhookInternal(Stream request) {
var response = new APIGatewayHttpApiV2ProxyResponse()
{
StatusCode = 200,
Body = new {
Status = 200,
Message = "OK",
}.ToJson()
};
var memoryStream = new MemoryStream();
using var streamWriter = new StreamWriter(memoryStream, leaveOpen: true);
streamWriter.Write(response.ToJson());
streamWriter.Flush();
memoryStream.Seek(0, SeekOrigin.Begin);
return memoryStream;
}
Now, the response in Testing Tool is the same as before:
{"statusCode":200,"headers":null,"cookies":null,"body":"{\"Status\":200,\"Message\":\"OK\"}","isBase64Encoded":false}
But when I run test request in lambda on AWS console, the response is
""
and API GW returns 500 with reason
The response from the Lambda function doesn't match the format that API Gateway expects. Lambda body is empty
but $context.responseLength reported by API GW is correctly 35 (length of body returned by lambda property before de-escaping).
I'm using an environment that doesn't have native support for a GCP client library. So I'm trying to figure out how to authenticate directly using manually crafted JWT token.
I've adapted the tasks from here Using nodeJS test environment, with jwa to implement the algorithm.
https://developers.google.com/identity/protocols/OAuth2ServiceAccount
The private key is taken from a JSON version of the service account file.
When the test runs, it catches a very basic 400 error, that just says "invalid request". I'm not sure how to troubleshoot it.
Could someone please help identify what I'm doing wrong?
var assert = require('assert');
const jwa = require('jwa');
const request = require('request-promise');
const pk = require('../auth/tradestate-2-tw').private_key;
const authEndpoint = 'https://www.googleapis.com/oauth2/v4/token';
describe('Connecting to Google API', function() {
it('should be able to get an auth token for Google Access', async () => {
assert(pk && pk.length, 'PK exists');
const header = { alg: "RS256", typ: "JWT" };
const body = {
"iss":"salesforce-treasury-wine#tradestate-2.iam.gserviceaccount.com",
"scope":"https://www.googleapis.com/auth/devstorage.readonly",
"aud":"https://www.googleapis.com/oauth2/v4/token",
"exp": new Date().getTime() + 3600 * 1000,
"iat": new Date().getTime()
};
console.log(JSON.stringify(body, null, 2));
const encodedHeader = Buffer.from(JSON.toString(header)).toString('base64')
const encodedBody = Buffer.from(JSON.toString(body)).toString('base64');
const cryptoString = `${encodedHeader}.${encodedBody}`;
const algo = jwa('RS256');
const signature = algo.sign(cryptoString, pk);
const jwt = `${encodedHeader}.${encodedBody}.${signature}`;
console.log('jwt', jwt);
const headers = {'Content-Type': 'application/x-www-form-urlencoded'};
const form = {
grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
assertion: jwt
};
try {
const result = await request.post({url: authEndpoint, form, headers});
assert(result, 'Reached result');
console.log('Got result', JSON.stringify(result, null, 2));
} catch (err) {
console.log(JSON.stringify(err, null, 2));
throw (err);
}
});
});
Use JSON.stringify instead of JSON.toString. From the link in your question:
{"alg":"RS256","typ":"JWT"}
The Base64url representation of this is as follows:
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9
When using JSON.toString() and then base64 encoding, you would get W29iamVjdCBKU09OXQ== which explains the 400 for an invalid request as it can't decrypt anything you're sending it.
I have configured an ASOS OpenIdConnect Server using and an asp.net core mvc app that uses the "Microsoft.AspNetCore.Authentication.OpenIdConnect": "1.0.0 and "Microsoft.AspNetCore.Authentication.Cookies": "1.0.0". I have tested the "Authorization Code" workflow and everything works.
The client web app processes the authentication as expected and creates a cookie storing the id_token, access_token, and refresh_token.
How do I force Microsoft.AspNetCore.Authentication.OpenIdConnect to request a new access_token when it expires?
The asp.net core mvc app ignores the expired access_token.
I would like to have openidconnect see the expired access_token then make a call using the refresh token to get a new access_token. It should also update the cookie values. If the refresh token request fails I would expect openidconnect to "sign out" the cookie (remove it or something).
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AutomaticAuthenticate = true,
AutomaticChallenge = true,
AuthenticationScheme = "Cookies"
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
{
ClientId = "myClient",
ClientSecret = "secret_secret_secret",
PostLogoutRedirectUri = "http://localhost:27933/",
RequireHttpsMetadata = false,
GetClaimsFromUserInfoEndpoint = true,
SaveTokens = true,
ResponseType = OpenIdConnectResponseType.Code,
AuthenticationMethod = OpenIdConnectRedirectBehavior.RedirectGet,
Authority = http://localhost:27933,
MetadataAddress = "http://localhost:27933/connect/config",
Scope = { "email", "roles", "offline_access" },
});
It seems there is no programming in the openidconnect authentication for asp.net core to manage the access_token on the server after received.
I found that I can intercept the cookie validation event and check if the access token has expired. If so, make a manual HTTP call to the token endpoint with the grant_type=refresh_token.
By calling context.ShouldRenew = true; this will cause the cookie to be updated and sent back to the client in the response.
I have provided the basis of what I have done and will work to update this answer once all work as been resolved.
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AutomaticAuthenticate = true,
AutomaticChallenge = true,
AuthenticationScheme = "Cookies",
ExpireTimeSpan = new TimeSpan(0, 0, 20),
SlidingExpiration = false,
CookieName = "WebAuth",
Events = new CookieAuthenticationEvents()
{
OnValidatePrincipal = context =>
{
if (context.Properties.Items.ContainsKey(".Token.expires_at"))
{
var expire = DateTime.Parse(context.Properties.Items[".Token.expires_at"]);
if (expire > DateTime.Now) //TODO:change to check expires in next 5 mintues.
{
logger.Warn($"Access token has expired, user: {context.HttpContext.User.Identity.Name}");
//TODO: send refresh token to ASOS. Update tokens in context.Properties.Items
//context.Properties.Items["Token.access_token"] = newToken;
context.ShouldRenew = true;
}
}
return Task.FromResult(0);
}
}
});
You must enable the generation of refresh_token by setting in startup.cs:
Setting values to AuthorizationEndpointPath = "/connect/authorize"; // needed for refreshtoken
Setting values to TokenEndpointPath = "/connect/token"; // standard token endpoint name
In your token provider, before validating the token request at the end of the HandleTokenrequest method, make sure you have set the offline scope:
// Call SetScopes with the list of scopes you want to grant
// (specify offline_access to issue a refresh token).
ticket.SetScopes(
OpenIdConnectConstants.Scopes.Profile,
OpenIdConnectConstants.Scopes.OfflineAccess);
If that is setup properly, you should receive a refresh_token back when you login with a password grant_type.
Then from your client you must issue the following request (I'm using Aurelia):
refreshToken() {
let baseUrl = yourbaseUrl;
let data = "client_id=" + this.appState.clientId
+ "&grant_type=refresh_token"
+ "&refresh_token=myRefreshToken";
return this.http.fetch(baseUrl + 'connect/token', {
method: 'post',
body : data,
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Accept': 'application/json'
}
});
}
and that's it, make sure that your auth provider in HandleRequestToken is not trying to manipulate the request that is of type refresh_token:
public override async Task HandleTokenRequest(HandleTokenRequestContext context)
{
if (context.Request.IsPasswordGrantType())
{
// Password type request processing only
// code that shall not touch any refresh_token request
}
else if(!context.Request.IsRefreshTokenGrantType())
{
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidGrant,
description: "Invalid grant type.");
return;
}
return;
}
The refresh_token shall just be able to pass through this method and is handled by another piece of middleware that handles refresh_token.
If you want more in depth knowledge about what the auth server is doing, you can have a look at the code of the OpenIdConnectServerHandler:
https://github.com/aspnet-contrib/AspNet.Security.OpenIdConnect.Server/blob/master/src/AspNet.Security.OpenIdConnect.Server/OpenIdConnectServerHandler.Exchange.cs
On the client side you must also be able to handle the auto refresh of the token, here is an example of an http interceptor for Angular 1.X, where one handles 401 reponses, refresh the token, then retry the request:
'use strict';
app.factory('authInterceptorService',
['$q', '$injector', '$location', 'localStorageService',
function ($q, $injector, $location, localStorageService) {
var authInterceptorServiceFactory = {};
var $http;
var _request = function (config) {
config.headers = config.headers || {};
var authData = localStorageService.get('authorizationData');
if (authData) {
config.headers.Authorization = 'Bearer ' + authData.token;
}
return config;
};
var _responseError = function (rejection) {
var deferred = $q.defer();
if (rejection.status === 401) {
var authService = $injector.get('authService');
console.log("calling authService.refreshToken()");
authService.refreshToken().then(function (response) {
console.log("token refreshed, retrying to connect");
_retryHttpRequest(rejection.config, deferred);
}, function () {
console.log("that didn't work, logging out.");
authService.logOut();
$location.path('/login');
deferred.reject(rejection);
});
} else {
deferred.reject(rejection);
}
return deferred.promise;
};
var _retryHttpRequest = function (config, deferred) {
console.log('autorefresh');
$http = $http || $injector.get('$http');
$http(config).then(function (response) {
deferred.resolve(response);
},
function (response) {
deferred.reject(response);
});
}
authInterceptorServiceFactory.request = _request;
authInterceptorServiceFactory.responseError = _responseError;
authInterceptorServiceFactory.retryHttpRequest = _retryHttpRequest;
return authInterceptorServiceFactory;
}]);
And here is an example I just did for Aurelia, this time I wrapped my http client into an http handler that checks if the token is expired or not. If it is expired it will first refresh the token, then perform the request. It uses a promise to keep the interface with the client-side data services consistent. This handler exposes the same interface as the aurelia-fetch client.
import {inject} from 'aurelia-framework';
import {HttpClient} from 'aurelia-fetch-client';
import {AuthService} from './authService';
#inject(HttpClient, AuthService)
export class HttpHandler {
constructor(httpClient, authService) {
this.http = httpClient;
this.authService = authService;
}
fetch(url, options){
let _this = this;
if(this.authService.tokenExpired()){
console.log("token expired");
return new Promise(
function(resolve, reject) {
console.log("refreshing");
_this.authService.refreshToken()
.then(
function (response) {
console.log("token refreshed");
_this.http.fetch(url, options).then(
function (success) {
console.log("call success", url);
resolve(success);
},
function (error) {
console.log("call failed", url);
reject(error);
});
}, function (error) {
console.log("token refresh failed");
reject(error);
});
}
);
}
else {
// token is not expired, we return the promise from the fetch client
return this.http.fetch(url, options);
}
}
}
For jquery you can look a jquery oAuth:
https://github.com/esbenp/jquery-oauth
Hope this helps.
Following on from #longday's answer, I have had success in using this code to force a client refresh without having to manually query an open id endpoint:
OnValidatePrincipal = context =>
{
if (context.Properties.Items.ContainsKey(".Token.expires_at"))
{
var expire = DateTime.Parse(context.Properties.Items[".Token.expires_at"]);
if (expire > DateTime.Now) //TODO:change to check expires in next 5 mintues.
{
context.ShouldRenew = true;
context.RejectPrincipal();
}
}
return Task.FromResult(0);
}
I am trying to set the IdentityServer4 authentication cookie lifetime.
This is my client configuration :
// OpenID Connect hybrid flow and client credentials client (MVC)
new Client
{
ClientId = "mvc",
ClientName = "MVC Client",
AllowedGrantTypes = GrantTypes.HybridAndClientCredentials,
IdentityTokenLifetime = 120,
AccessTokenLifetime = 120,
AuthorizationCodeLifetime = 120,
ClientSecrets = new List<Secret>
{
new Secret("secret".Sha256())
},
RedirectUris = new List<string>
{
"http://localhost:5002/signin-oidc"
},
PostLogoutRedirectUris = new List<string>
{
"http://localhost:5002"
},
AllowedScopes = new List<string>
{
StandardScopes.OpenId.Name,
StandardScopes.Profile.Name,
StandardScopes.OfflineAccess.Name,
"api1"
}
}
and my Configure method in the mvc client is
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationScheme = "Cookies",
AutomaticChallenge = true,
ExpireTimeSpan = System.TimeSpan.FromSeconds(120),
SlidingExpiration = false
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
{
AuthenticationScheme = "oidc",
SignInScheme = "Cookies",
Authority = "http://localhost:5000",
RequireHttpsMetadata = false,
ClientId = "mvc",
ClientSecret = "secret",
ResponseType = "code id_token",
Scope = { "api1", "offline_access" },
GetClaimsFromUserInfoEndpoint = true,
SaveTokens = true
});
app.UseStaticFiles();
app.UseMvcWithDefaultRoute();
}
I'm using the below sample from IdentityServer4 samples to learn IdentityServer4.
IdentityServer4.Samples/Quickstarts/5_HybridFlowAuthenticationWithApiAccess
I have already set the cookie expire time, access token life time, identity token life time and authorization code life time. But still the cookie life time is showing as session in the browser. Please see the below image
Am I missed any setting to do?
Any help is greatly appreciated.
I send a HTTP Get Request with a Basic Authentification to the Login-Endpoint of the Host:
request = new HttpRequestMessage();
// Configuration Item: Login URL Suffix
request.RequestUri = new Uri(string.Format("https://{0}/{1}", Host, loginSuffix));
request.Method = Windows.Web.Http.HttpMethod.Get;
var info = User + ":" + Password;
var token = Convert.ToBase64String(Encoding.UTF8.GetBytes(info));
request.Headers.Authorization = new HttpCredentialsHeaderValue("Basic", token);
_httpClient = CreateHttpClient(ref cookieManager);
response = await _httpClient.SendRequestAsync(request, HttpCompletionOption.ResponseContentRead);
response.EnsureSuccessStatusCode();
responseBodyAsText = await response.Content.ReadAsStringAsync();
The HttpClient is created with a Filter, to set Cookies later:
private HttpClient CreateHttpClient(ref HttpCookieManager _cookieManager)
{
HttpBaseProtocolFilter _filter = new HttpBaseProtocolFilter();
HttpClient _httpClient = new Windows.Web.Http.HttpClient(_filter);
_cookieManager = _filter.CookieManager;
return _httpClient;
}
From the Response the SET-COOKIE Header can be read.
string[] Queries;
response.Headers.TryGetValue("Set-Cookie", out tmpString);
if (tmpString != null)
Queries = tmpString.Split(new string[] { ";" }, StringSplitOptions.RemoveEmptyEntries);
I´m looking for a Cookie with a defined Name (CookieKeyName), which will be set in the next Request.
foreach (var query in Queries)
{
if (query.Contains(CookieKeyName))
{
staticCookieKey = query.Substring(0, query.IndexOf("="));
staticCookieValue = query.Substring(query.IndexOf("=") + 1);
}
}
I would expect, that the HttpClient will use the received Set-Cookie in the response for this URL as Cookie in every following Request automatically.
I´m preparing the next Request and setting the Cookie by myself:
request.RequestUri = new Uri(string.Format("https://{0}/qcbin", Host));
request.Method = Windows.Web.Http.HttpMethod.Get;
HttpCookie _cookie = new Windows.Web.Http.HttpCookie(staticCookieKey, Host, "/");
_cookie.Value = staticCookieValue;
bool replaced = cookieManager.SetCookie(_cookie);
The following Sending of the Requests provides to a Web Exception 401, because the Server expects for this URL the previously in the Response received Cookie.
response = await _httpClient.SendRequestAsync(request, HttpCompletionOption.ResponseContentRead);
response.EnsureSuccessStatusCode();
responseBodyAsText = await response.Content.ReadAsStringAsync();
Looking with Fiddler on the Line, the second Request contains no Cookie Header. Even Setting of the Cookie in CookieManager nor the proceeding of the Set-Cookie i the first Response by the HttpClient is working.
Hint: The length of the value of the Cookies is about 6000 chars (coming from a IBM Data Power).
Thank you in advance for help.